Files
ofdgo/ofdgo_render_compile.go
T
2026-10-06 17:53:24 +08:00

547 lines
18 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 (
"fmt"
"math"
"strconv"
"strings"
)
// semanticCompiler 将公共页面语义编译为路径和原始图片,绘制、搜索和度量共享定位
type semanticCompiler struct {
renderer *Renderer
geometry GeometryBackend
page *RasterPage
text PageText
measurement ObjectMeasurement
measure, contours, textOnly, pattern bool
patterns *renderCache[patternCellKey, []RasterCommand]
patternReferences map[*Pattern]bool
patternPage *Matrix
}
// semanticStyle 保存已继承的路径颜色和描边参数
type semanticStyle struct {
fill, stroke *FillColor
options StrokeOptions
}
// DrawObject 解释叶子对象,不调用任何具体页面编译器
// 入参: object 源对象, state 继承状态
// 返回: error 编译错误
func (c *semanticCompiler) DrawObject(object *GraphicObject, state RenderState) error {
switch object.Type {
case "TextObject":
return c.textObject(object.TextObject, state)
case "PathObject":
if !c.textOnly {
return c.pathObject(object.PathObject, state)
}
case "ImageObject":
if !c.textOnly {
return c.imageObject(object.ImageObject, state)
}
default:
return fmt.Errorf("unsupported graphic object %q", object.Type)
}
return nil
}
// newSemanticCompiler 绑定当前字体和几何能力
// 入参: r 渲染器
// 返回: *semanticCompiler 编译器, error 能力错误
func newSemanticCompiler(r *Renderer) (*semanticCompiler, error) {
geometry, err := r.Geometry()
if err != nil {
return nil, err
}
return &semanticCompiler{renderer: r, geometry: geometry, patterns: &renderCache[patternCellKey, []RasterCommand]{limit: 8 << 20}}, nil
}
// segments 转换光栅曲线,仅在输出边界展开椭圆弧
// 入参: path 页面路径
// 返回: []RasterSegment 光栅路径, error 能力或路径错误
func (c *semanticCompiler) segments(path GeometryPath) ([]RasterSegment, error) {
for _, segment := range path {
if segment.Verb == GeometryArc {
curves, ok := c.geometry.(GeometryCurves)
if !ok {
return nil, fmt.Errorf("geometry curves: %w", ErrBackendUnavailable)
}
var err error
path, err = curves.Curves(path)
if err != nil {
return nil, err
}
break
}
}
result := make([]RasterSegment, 0, len(path))
for _, s := range path {
segment := RasterSegment{End: RasterPoint{s.End.X, s.End.Y}, Control1: RasterPoint{s.Control1.X, s.Control1.Y}, Control2: RasterPoint{s.Control2.X, s.Control2.Y}}
switch s.Verb {
case GeometryMove:
segment.Verb = RasterMove
case GeometryLine:
segment.Verb = RasterLine
case GeometryQuad:
segment.Verb = RasterQuad
case GeometryCubic:
segment.Verb = RasterCubic
case GeometryClose:
segment.Verb = RasterClose
default:
return nil, fmt.Errorf("unsupported raster geometry %d", s.Verb)
}
result = append(result, segment)
}
return result, nil
}
// addBounds 收集精确范围和独立轮廓,不分配页面像素
// 入参: path 页面路径, evenOdd 奇偶规则
// 返回: error 几何错误
func (c *semanticCompiler) addBounds(path GeometryPath, evenOdd bool) error {
if len(path) == 0 {
return nil
}
box, err := c.geometry.Bounds(path)
if err != nil {
return err
}
c.measurement.Bounds = unionTextBox(c.measurement.Bounds, box)
if c.contours {
svg, err := closedGeometry(path).SVG()
if err != nil {
return err
}
c.measurement.Contours = append(c.measurement.Contours, ObjectContour{Path: svg, EvenOdd: evenOdd})
}
return nil
}
// fill 应用对象裁剪、底纹和渐变,不改变输入路径
// 入参: path 页面路径, paint 画刷, evenOdd 奇偶规则, clip 对象裁剪, matrix 图案矩阵, shadingMatrix 渐变矩阵
// 返回: error 几何或编译错误
func (c *semanticCompiler) fill(path GeometryPath, paint Paint, evenOdd bool, clip *GeometryPath, matrix, shadingMatrix Matrix) error {
if len(path) == 0 || paint.Kind == PaintNone || paint.Kind == PaintSolid && paint.Color.A == 0 {
return nil
}
path = closedGeometry(path)
if !c.measure && paint.Kind != PaintPattern {
return c.command(path, paint, evenOdd, clip, shadingMatrix, nil)
}
var err error
if evenOdd && (clip != nil || paint.Kind == PaintPattern || paint.Gradient != nil || paint.Mesh != nil) {
path, err = c.geometry.Normalize(path, true)
if err != nil {
return err
}
evenOdd = false
}
path, err = clipGeometry(c.geometry, path, clip)
if err != nil || len(path) == 0 {
return err
}
if paint.Gradient != nil {
path, err = c.shadingClip(path, paint, shadingMatrix)
if err != nil || len(path) == 0 {
return err
}
}
if c.measure {
if paint.Mesh != nil && paint.Mesh.Background.A == 0 {
outline, err := c.geometry.Normalize(paint.Mesh.outline(), false)
if err != nil {
return err
}
outline, err = c.geometry.Transform(outline, shadingMatrix)
if err != nil {
return err
}
path, err = clipGeometry(c.geometry, path, &outline)
if err != nil {
return err
}
}
return c.addBounds(path, evenOdd)
}
if paint.Kind == PaintPattern {
return c.fillPattern(path, paint.Pattern, matrix)
}
return nil
}
// command 保留路径、画刷与绘制裁剪,不提前求几何交集或展开描边
// 入参: path 页面路径, paint 画刷, evenOdd 奇偶规则, clip 绘制裁剪, shadingMatrix 渐变矩阵, stroke 页面描边样式
// 返回: error 路径或矩阵错误
func (c *semanticCompiler) command(path GeometryPath, paint Paint, evenOdd bool, clip *GeometryPath, shadingMatrix Matrix, stroke *StrokeOptions) error {
var err error
command := RasterCommand{Paint: RasterPaint{Color: paint.Color}, EvenOdd: evenOdd, Transform: RasterMatrix{1, 0, 0, 1, 0, 0}}
command.Stroke = stroke
if clip != nil {
command.Clip, err = c.segments(*clip)
if err != nil {
return err
}
}
if paint.Gradient != nil {
gradient, view := semanticGradient(paint, shadingMatrix)
inverse, ok := view.Invert()
if !ok {
return fmt.Errorf("invalid shading transform")
}
path, err = c.geometry.Transform(path, inverse)
if err != nil {
return err
}
command.Transform = RasterMatrix(view.Values())
command.Paint.Gradient = gradient
}
if paint.Mesh != nil {
inverse, ok := shadingMatrix.Invert()
if !ok {
return fmt.Errorf("invalid mesh shading transform")
}
path, err = c.geometry.Transform(path, inverse)
if err != nil {
return err
}
command.Transform = RasterMatrix(shadingMatrix.Values())
command.Paint.Gradient = &RasterGradient{Kind: RasterMesh, Mesh: paint.Mesh}
}
command.Path, err = c.segments(path)
if err != nil {
return err
}
c.page.Commands = append(c.page.Commands, command)
return nil
}
// stroke 将普通描边交给绘制后端,度量和底纹仍使用精确几何轮廓
// 入参: path 页面路径, paint 画刷, options 页面描边样式, clip 对象裁剪, matrix 图案矩阵, shadingMatrix 渐变矩阵
// 返回: error 描边或编译错误
func (c *semanticCompiler) stroke(path GeometryPath, paint Paint, options StrokeOptions, clip *GeometryPath, matrix, shadingMatrix Matrix) error {
if len(path) == 0 || paint.Kind == PaintNone || paint.Kind == PaintSolid && paint.Color.A == 0 {
return nil
}
if !c.measure && paint.Kind != PaintPattern {
return c.command(path, paint, false, clip, shadingMatrix, &options)
}
outline, err := c.geometry.Stroke(path, options)
if err != nil {
return err
}
return c.fill(outline, paint, false, clip, matrix, shadingMatrix)
}
// semanticGradient 保留渐变周期、延伸与椭圆坐标
// 入参: paint 渐变画刷, matrix 渐变父矩阵
// 返回: *RasterGradient 光栅渐变, Matrix 局部到页面矩阵
func semanticGradient(paint Paint, matrix Matrix) (*RasterGradient, Matrix) {
s := paint.Gradient
extend, _ := strconv.Atoi(s.Extend)
period := 1.0
length := math.Hypot(s.End.X-s.Start.X, s.End.Y-s.Start.Y)
if s.MapUnit > 0 && length > 0 {
period = s.MapUnit / length
}
g := &RasterGradient{Start: RasterPoint{s.Start.X, s.Start.Y}, End: RasterPoint{s.End.X, s.End.Y}, R0: s.StartRadius, R1: s.EndRadius, Stops: s.Stops, Spread: &RasterSpread{Extend: extend, MapType: s.MapType, Period: period}}
g.Sample = s.Sample
if paint.Kind == PaintRadial {
g.Kind = RasterRadial
if s.Eccentricity > 0 && s.Eccentricity < 1 {
angle := s.Angle * math.Pi / 180
view := TranslationMatrix(s.Start.X, s.Start.Y).Multiply(Matrix{a: math.Cos(angle), b: math.Sin(angle), c: -math.Sin(angle), d: math.Cos(angle)}).Multiply(Matrix{a: 1, d: math.Sqrt(1 - s.Eccentricity*s.Eccentricity)}).Multiply(TranslationMatrix(-s.Start.X, -s.Start.Y))
inverse, _ := view.Invert()
g.End.X, g.End.Y = inverse.Transform(g.End.X, g.End.Y)
matrix = matrix.Multiply(view)
}
}
return g, matrix
}
// objectStyle 合并继承与对象样式,不修改共享绘制参数
// 入参: object 路径样式, defaults 继承样式, linear 组合变换
// 返回: semanticStyle 解析样式
func (c *semanticCompiler) objectStyle(object PathObject, defaults *DrawParam, linear Matrix) semanticStyle {
defaults = c.renderer.drawParamDefaults(object.DrawParam, defaults)
style := semanticStyle{options: StrokeOptions{Width: defaultPathLineWidth, Cap: "Butt", Join: "Miter", MiterLimit: defaultMiterLimit}}
if defaults != nil {
style.fill = defaults.FillColor
style.stroke = (*FillColor)(defaults.StrokeColor)
if defaults.LineWidth > 0 || defaults.LineWidthSet {
style.options.Width = defaults.LineWidth
}
if defaults.Cap != "" {
style.options.Cap = defaults.Cap
}
if defaults.Join != "" {
style.options.Join = defaults.Join
}
if defaults.MiterLimit > 0 {
style.options.MiterLimit = defaults.MiterLimit
}
style.options.Dashes = parseFloats(defaults.DashPattern)
if defaults.DashOffset != nil {
style.options.DashOffset = *defaults.DashOffset
}
}
if object.FillColor != nil {
style.fill = object.FillColor
}
if object.StrokeColor != nil {
style.stroke = (*FillColor)(object.StrokeColor)
}
if object.LineWidth > 0 || object.LineWidthSet {
style.options.Width = object.LineWidth
}
if object.Cap != "" {
style.options.Cap = object.Cap
}
if object.Join != "" {
style.options.Join = object.Join
}
if object.MiterLimit > 0 {
style.options.MiterLimit = object.MiterLimit
}
if object.DashPattern != "" || object.dashPatternSet {
style.options.Dashes = parseFloats(object.DashPattern)
}
if object.DashOffset != nil {
style.options.DashOffset = *object.DashOffset
}
style.fill = withFillAlpha(style.fill, object.Alpha)
style.stroke = withFillAlpha(style.stroke, object.Alpha)
if scale := math.Sqrt(math.Abs(linear.a*linear.d - linear.b*linear.c)); scale > 0 {
style.options.Width *= scale
style.options.DashOffset *= scale
for i := range style.options.Dashes {
style.options.Dashes[i] *= scale
}
}
if style.options.Width == 0 {
style.options.Width = 25.4 / c.renderer.DPI
}
return style
}
// pathObject 编译填充、描边和虚线路径
// 入参: object 路径对象, state 继承状态
// 返回: error 路径或画刷错误
func (c *semanticCompiler) pathObject(object PathObject, state RenderState) error {
if object.Visible != nil && !*object.Visible {
return nil
}
matrix, linear := renderObjectMatrix(object.Boundary, NewMatrix(object.CTM), state)
local := object
local.Boundary = "0 0 1 1"
local.CTM = ""
var path GeometryPath
var err error
tinyMatrix := matrix
if state.BoundaryInCTM && state.Parent != nil {
tinyMatrix, _ = renderObjectMatrix(object.Boundary, NewMatrix(object.CTM), RenderState{})
}
path = tinyFillRectPath(object, tinyMatrix)
if path != nil {
if state.BoundaryInCTM && state.Parent != nil {
path, err = c.geometry.Transform(path, *state.Parent)
}
} else {
path, err = c.geometry.Path(local)
if err != nil {
return err
}
path, err = c.geometry.Transform(path, matrix)
}
if err != nil {
return err
}
clip, err := c.renderer.objectGeometryClip(object.Clips, object.Boundary, NewMatrix(object.CTM), state)
if err != nil {
return err
}
style := c.objectStyle(object, state.Defaults, linear)
if style.stroke == nil {
style.stroke = withFillAlpha(&FillColor{}, object.Alpha)
}
shading, _ := renderObjectMatrix(object.Boundary, IdentityMatrix, state)
if object.Fill != nil && *object.Fill {
paint, err := c.renderer.ResolvePaint(style.fill)
if err != nil {
return err
}
if err := c.fill(path, paint, object.Rule == "Even-Odd", clip, matrix, shading); err != nil {
return err
}
}
if object.Stroke == nil || *object.Stroke {
paint, err := c.renderer.ResolvePaint(style.stroke)
if err != nil {
return err
}
return c.stroke(path, paint, style.options, clip, matrix, shading)
}
return nil
}
// textObject 共用字形定位处理绘制、原文提取和轮廓
// 入参: object 文字对象, state 继承状态
// 返回: error 字体或几何错误
func (c *semanticCompiler) textObject(object TextObject, state RenderState) error {
if object.Visible != nil && !*object.Visible || object.Fill != nil && !*object.Fill && (object.Stroke == nil || !*object.Stroke) {
return nil
}
positioned, err := c.renderer.PositionText(object, state)
if err != nil {
return err
}
if !c.pattern && positioned.Run.Text != "" {
c.text.Runs = append(c.text.Runs, positioned.Run)
}
if c.textOnly {
return nil
}
if c.measure && !c.contours {
for _, box := range positioned.Run.Boxes {
c.measurement.Bounds = unionTextBox(c.measurement.Bounds, box)
}
return nil
}
_, linear := renderObjectMatrix(object.Boundary, NewMatrix(object.CTM), state)
style := c.objectStyle(PathObject{DrawParam: object.DrawParam, FillColor: object.FillColor, StrokeColor: object.StrokeColor, Alpha: object.Alpha, LineWidth: object.LineWidth, LineWidthSet: object.LineWidthSet, Join: object.Join, MiterLimit: object.MiterLimit}, state.Defaults, linear)
if style.fill == nil {
style.fill = withFillAlpha(&FillColor{}, object.Alpha)
}
shading, _ := renderObjectMatrix(object.Boundary, IdentityMatrix, state)
fill, err := c.renderer.ResolvePaint(style.fill)
if err != nil {
return err
}
var stroke Paint
if object.Stroke != nil && *object.Stroke {
stroke, err = c.renderer.ResolvePaint(style.stroke)
if err != nil {
return err
}
}
for _, glyph := range positioned.Glyphs {
if object.Fill == nil || *object.Fill {
if err := c.fill(glyph.Path, fill, false, positioned.Clip, positioned.Matrix, shading); err != nil {
return err
}
}
if object.Stroke != nil && *object.Stroke && len(glyph.Path) > 0 {
if err := c.stroke(glyph.Path, stroke, style.options, positioned.Clip, positioned.Matrix, shading); err != nil {
return err
}
}
if err := c.fill(glyph.Underline, fill, false, positioned.Clip, positioned.Matrix, shading); err != nil {
return err
}
}
return nil
}
// fillPattern 在对象裁剪区域内展开底纹,保留单元反射和继承样式
// 入参: clip 绘制区域, pattern 底纹, object 对象矩阵
// 返回: error 编译错误
func (c *semanticCompiler) fillPattern(clip GeometryPath, pattern *PatternPaint, object Matrix) error {
xstep, ystep := math.Max(pattern.XStep, pattern.Width), math.Max(pattern.YStep, pattern.Height)
if xstep <= 0 || ystep <= 0 {
return fmt.Errorf("invalid pattern cell size")
}
matrix := NewMatrix(pattern.CTM)
if pattern.RelativeTo != "Page" {
matrix = object.Multiply(matrix)
} else if c.patternPage != nil {
matrix = c.patternPage.Multiply(matrix)
}
inverse, ok := matrix.Invert()
if !ok {
return fmt.Errorf("invalid pattern transform")
}
if pattern.CellContent.empty() {
return nil
}
box, err := c.geometry.Bounds(clip)
if err != nil {
return err
}
box = inverse.TransformBox(box)
segments, err := c.segments(clip)
if err != nil {
return err
}
for x, end := int(math.Floor(box.X/xstep))-1, int(math.Ceil((box.X+box.W)/xstep))+1; x <= end; x++ {
for y, end := int(math.Floor(box.Y/ystep))-1, int(math.Ceil((box.Y+box.H)/ystep))+1; y <= end; y++ {
tile := matrix.Multiply(TranslationMatrix(float64(x)*xstep, float64(y)*ystep))
if x%2 != 0 && (pattern.ReflectMethod == "Column" || pattern.ReflectMethod == "RowAndColumn") {
tile = tile.Multiply(Matrix{a: -1, d: 1, e: pattern.Width})
}
if y%2 != 0 && (pattern.ReflectMethod == "Row" || pattern.ReflectMethod == "RowAndColumn") {
tile = tile.Multiply(Matrix{a: 1, d: -1, f: pattern.Height})
}
commands, err := c.patternCell(pattern, tile)
if err != nil {
return err
}
if err := c.appendPatternCell(commands, clip, segments); err != nil {
return err
}
}
}
return nil
}
// tinyFillRectPath 保留历史文档中微小填充块的缺省末端处理
// 入参: object 路径对象, matrix 不含外层变换的页面矩阵
// 返回: GeometryPath 修正路径,非匹配格式时为空
func tinyFillRectPath(object PathObject, matrix Matrix) GeometryPath {
if object.Fill == nil || !*object.Fill || object.Stroke == nil || *object.Stroke {
return nil
}
box, err := ParseBox(object.Boundary)
if err != nil || box.W <= 0 || box.H <= 0 || box.W > .6 || box.H > .6 {
return nil
}
tokens := strings.Fields(object.AbbreviatedData)
if len(tokens) != 11 || tokens[0] != "M" || tokens[3] != "L" || tokens[6] != "L" || tokens[9] != "L" || tokens[10] != "C" {
return nil
}
var minX, minY, maxX, maxY float64
for i := 1; i < 10; i += 3 {
x, errX := strconv.ParseFloat(tokens[i], 64)
y, errY := strconv.ParseFloat(tokens[i+1], 64)
if errX != nil || errY != nil {
return nil
}
x, y = matrix.Transform(x, y)
if i == 1 {
minX, maxX, minY, maxY = x, x, y, y
} else {
minX = math.Min(minX, x)
maxX = math.Max(maxX, x)
minY = math.Min(minY, y)
maxY = math.Max(maxY, y)
}
}
expand := math.Min(box.W, box.H) * .08
return geometryRectangle(Box{X: minX - expand, Y: minY - expand, W: maxX - minX + 2*expand, H: maxY - minY + 2*expand})
}