mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:16:55 +08:00
234 lines
8.2 KiB
Go
234 lines
8.2 KiB
Go
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ofdgo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"io"
|
|
"reflect"
|
|
)
|
|
|
|
const (
|
|
SVGEmbedded SVGMode = iota
|
|
SVGExternalFonts
|
|
SVGExternalResources
|
|
SVGObjects
|
|
)
|
|
|
|
// ErrBackendUnavailable 表示未配置所需后端,不会自动切换其他实现
|
|
var ErrBackendUnavailable = errors.New("render backend unavailable")
|
|
|
|
// Backend 提供稳定的后端标识,不包含前端文案
|
|
type Backend interface {
|
|
Name() string
|
|
}
|
|
|
|
// RasterBackend 消费只读页面,返回独立图像,不得静默跳过指令或切换其他后端
|
|
type RasterBackend interface {
|
|
Backend
|
|
Render(*RasterPage) (image.Image, error)
|
|
}
|
|
|
|
// PageCompiler 统一页面编译、文字提取和对象度量,不得修改源数据
|
|
// 三项能力需采用相同的字体和几何语义,避免绘制、搜索与选区不一致
|
|
// renderer提供文档、字体配置和文字回调,实现中不得递归调用对应的Renderer方法
|
|
// CompilePage在配置OnPageText时需同步回报文字,PageText和MeasureObject不触发回调
|
|
type PageCompiler interface {
|
|
Backend
|
|
CompilePage(renderer *Renderer, page *PageContent) (*RasterPage, error)
|
|
PageText(renderer *Renderer, page *PageContent) (*PageText, error)
|
|
MeasureObject(renderer *Renderer, object GraphicObject, options MeasureOptions) (ObjectMeasurement, error)
|
|
}
|
|
|
|
// MeasureOptions 描述对象度量上下文,坐标以页面左上角为原点,单位为毫米
|
|
// Defaults为继承样式,Parent为父变换,BoundaryInCTM指定边界是否参与父变换
|
|
// Clip为页面坐标中的非零填充裁剪,nil表示不裁剪,空路径表示全部裁去
|
|
// Contours决定是否收集填充轮廓,关闭时文字采用逐字符范围
|
|
type MeasureOptions struct {
|
|
Defaults *DrawParam
|
|
Parent *Matrix
|
|
BoundaryInCTM bool
|
|
Clip *GeometryPath
|
|
Contours bool
|
|
}
|
|
|
|
// ObjectMeasurement 保存对象在页面坐标中的范围与可选轮廓
|
|
type ObjectMeasurement struct {
|
|
Bounds Box
|
|
Contours []ObjectContour
|
|
}
|
|
|
|
// SVGMode 指定SVG资源封装方式,Objects模式还需保留稳定的对象分组
|
|
type SVGMode uint8
|
|
|
|
// SVGBackend 输出SVG及资源,不得修改源页面或省略不支持的内容
|
|
// renderer提供文档和排版能力,输出时不得再次调用RenderToSVG系列方法
|
|
type SVGBackend interface {
|
|
Backend
|
|
RenderSVG(renderer *Renderer, page *PageContent, writer io.Writer, mode SVGMode) (SVGResources, error)
|
|
}
|
|
|
|
// RenderDocumentPage 保存已解析页面和物理区域,不得修改源页面内容
|
|
type RenderDocumentPage struct {
|
|
Content *PageContent
|
|
Box Box
|
|
}
|
|
|
|
// PDFBackend 输出完整PDF,保留文字、链接、目录和文档信息
|
|
// pages交由后端消费,可逐页清空Content以释放引用,不得改变页面顺序
|
|
// progress返回错误时应停止输出,调用方在失败后丢弃输出
|
|
type PDFBackend interface {
|
|
Backend
|
|
RenderPDF(renderer *Renderer, pages []RenderDocumentPage, writer io.Writer, progress func(int, int) error) error
|
|
}
|
|
|
|
// EPSBackend 输出单页EPS,不得修改源页面
|
|
type EPSBackend interface {
|
|
Backend
|
|
RenderEPS(renderer *Renderer, page *PageContent, writer io.Writer) error
|
|
}
|
|
|
|
// RenderBackends 按职责组合后端,nil表示明确禁用,不隐式回退
|
|
// 实例及其Renderer需串行使用,第三方实现无需引用任何内置绘图库
|
|
type RenderBackends struct {
|
|
FontResources FontResourceBackend
|
|
Fonts FontBackend
|
|
Geometry GeometryBackend
|
|
Compiler PageCompiler
|
|
Raster RasterBackend
|
|
SVG SVGBackend
|
|
PDF PDFBackend
|
|
EPS EPSBackend
|
|
}
|
|
|
|
// BackendInfo 描述各项能力实际使用的后端,空标识表示未配置
|
|
type BackendInfo struct {
|
|
FontResources string `json:"fontResources"`
|
|
Fonts string `json:"fonts"`
|
|
Geometry string `json:"geometry"`
|
|
Compiler string `json:"compiler"`
|
|
Raster string `json:"raster"`
|
|
SVG string `json:"svg"`
|
|
PDF string `json:"pdf"`
|
|
EPS string `json:"eps"`
|
|
}
|
|
|
|
// Info 返回配置中各能力的提供者,不依赖WebUI推断
|
|
// 返回: BackendInfo 后端能力
|
|
func (b RenderBackends) Info() BackendInfo {
|
|
return BackendInfo{FontResources: backendName(b.FontResources), Fonts: backendName(b.Fonts), Geometry: backendName(b.Geometry), Compiler: backendName(b.Compiler), Raster: backendName(b.Raster), SVG: backendName(b.SVG), PDF: backendName(b.PDF), EPS: backendName(b.EPS)}
|
|
}
|
|
|
|
// WithRenderBackends 替换完整后端组合,未配置的能力不会沿用默认实现
|
|
// 入参: backends 后端组合
|
|
// 返回: RendererOption 渲染选项
|
|
func WithRenderBackends(backends RenderBackends) RendererOption {
|
|
return func(r *Renderer) {
|
|
fontsChanged := !sameBackend(r.backends.Fonts, backends.Fonts)
|
|
for _, pair := range [][2]Backend{
|
|
{r.backends.FontResources, backends.FontResources}, {r.backends.Fonts, backends.Fonts},
|
|
{r.backends.Geometry, backends.Geometry}, {r.backends.Compiler, backends.Compiler},
|
|
{r.backends.Raster, backends.Raster}, {r.backends.SVG, backends.SVG},
|
|
{r.backends.PDF, backends.PDF}, {r.backends.EPS, backends.EPS},
|
|
} {
|
|
if !sameBackend(pair[0], pair[1]) {
|
|
r.backendStates = make(map[any]any)
|
|
break
|
|
}
|
|
}
|
|
r.backends = backends
|
|
if fontsChanged {
|
|
r.resetFontBackendCache()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Backends 返回当前后端配置副本,可修改后通过WithRenderBackends应用
|
|
// 返回: RenderBackends 后端组合
|
|
func (r *Renderer) Backends() RenderBackends {
|
|
return r.backends
|
|
}
|
|
|
|
// WithRasterBackend 选择当前渲染器的图像、PNG和JPEG后端,不改变SVG、PDF或OFD保存
|
|
// 入参: backend 光栅后端,nil禁用图像输出
|
|
// 返回: RendererOption 渲染选项
|
|
func WithRasterBackend(backend RasterBackend) RendererOption {
|
|
return func(r *Renderer) {
|
|
if !sameBackend(r.backends.Raster, backend) {
|
|
r.backendStates = make(map[any]any)
|
|
}
|
|
r.backends.Raster = backend
|
|
}
|
|
}
|
|
|
|
// CompilePage 编译与后端无关的光栅页面,可交给任意RasterBackend绘制
|
|
// 字形、描边、裁剪和底纹沿用文档语义,不交由后端重新排版
|
|
// 入参: page 页面内容
|
|
// 返回: *RasterPage 只读绘制页面, error 编译错误
|
|
func (r *Renderer) CompilePage(page *PageContent) (*RasterPage, error) {
|
|
if r.backends.Compiler == nil {
|
|
return nil, fmt.Errorf("page compiler: %w", ErrBackendUnavailable)
|
|
}
|
|
return r.backends.Compiler.CompilePage(r, page)
|
|
}
|
|
|
|
// backendName 获取已配置的后端标识
|
|
// 入参: backend 后端实例
|
|
// 返回: string 后端标识,未配置时为空
|
|
func backendName(backend Backend) string {
|
|
if backend == nil {
|
|
return ""
|
|
}
|
|
return backend.Name()
|
|
}
|
|
|
|
// sameBackend 判断不可变值配置是否未变,包含指针的配置重新应用时刷新可变状态
|
|
// 入参: left 原后端, right 新后端
|
|
// 返回: bool 是否为同一配置
|
|
func sameBackend(left, right Backend) bool {
|
|
if left == nil || right == nil {
|
|
return left == nil && right == nil
|
|
}
|
|
a, b := reflect.ValueOf(left), reflect.ValueOf(right)
|
|
return a.Comparable() && b.Comparable() && left == right && immutableBackendValue(a)
|
|
}
|
|
|
|
// immutableBackendValue 检查嵌套配置是否仅包含不可变值,不绑定具体后端类型
|
|
// 入参: value 配置值
|
|
// 返回: bool 是否可按值比较复用
|
|
func immutableBackendValue(value reflect.Value) bool {
|
|
switch value.Kind() {
|
|
case reflect.Pointer, reflect.UnsafePointer, reflect.Chan, reflect.Map, reflect.Slice, reflect.Func:
|
|
return false
|
|
case reflect.Interface:
|
|
return value.IsNil() || immutableBackendValue(value.Elem())
|
|
case reflect.Struct:
|
|
for i := range value.NumField() {
|
|
if !immutableBackendValue(value.Field(i)) {
|
|
return false
|
|
}
|
|
}
|
|
case reflect.Array:
|
|
for i := range value.Len() {
|
|
if !immutableBackendValue(value.Index(i)) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|