mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
368 lines
13 KiB
Go
368 lines
13 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"
|
|
basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom"
|
|
)
|
|
|
|
// RenderState 保存对象继承的样式、父变换和页面坐标裁剪,所有引用只读
|
|
// Clip为nil表示未提供裁剪,空路径表示完全裁去,单位为毫米
|
|
// Hidden表示当前对象或其上层容器不可见,不改变源对象的Visible属性
|
|
type RenderState struct {
|
|
Defaults *DrawParam
|
|
Parent *Matrix
|
|
BoundaryInCTM bool
|
|
Hidden bool
|
|
Clip *GeometryPath
|
|
}
|
|
|
|
// PageVisitor 消费按标准图层顺序展开的对象与印章,源对象和状态只读
|
|
// 保留引用时文档也须保持不变,坐标与属性继续遵循OFD语义
|
|
// 复合图元的变换、透明度和引用由公共遍历处理,具体对象外观由访问器解释
|
|
type PageVisitor interface {
|
|
DrawObject(object *GraphicObject, state RenderState) error
|
|
DrawStamp(stamp Stamp) error
|
|
}
|
|
|
|
// PageGroups 提供可选的对象及注解分组,返回true时配对调用EndObject
|
|
// SkipObjects保持不可见复合图元的成员编号,供可编辑矢量输出使用
|
|
type PageGroups interface {
|
|
BeginObject(object *GraphicObject) bool
|
|
BeginAnnotation(id string) bool
|
|
EndObject()
|
|
SkipObjects(count int)
|
|
}
|
|
|
|
// PageComposites 可选地接收展开前的复合图元及矢量资源,不替代成员遍历
|
|
// 状态包含当前图元到页面的完整变换,源对象及状态只读
|
|
type PageComposites interface {
|
|
VisitComposite(object *CompositeGraphicUnit, state RenderState) error
|
|
}
|
|
|
|
// PageAnalysis 控制WalkPage和WalkObject对隐藏内容及裁剪的处理,不影响已构建场景的重放
|
|
// 跳过裁剪时RenderState.Clip为空,源对象的裁剪定义不变,叶对象仍由访问器解释
|
|
type PageAnalysis interface {
|
|
IncludeHidden() bool
|
|
SkipClips() bool
|
|
}
|
|
|
|
// WalkPage 按模板、图层、注解和印章顺序访问页面,不依赖任何绘图库
|
|
// 页面背景由调用方处理,文字裁剪可通过几何后端加载字体,图片由访问器按需加载
|
|
// 已读取文档结构时,先完成延迟的注解与签名加载
|
|
// 入参: page 页面内容, visitor 页面访问器
|
|
// 返回: error 遍历或访问器错误
|
|
func (r *Renderer) WalkPage(page *PageContent, visitor PageVisitor) error {
|
|
if r.pageContext != nil {
|
|
if err := r.pageContext.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.Reader.relatedPending {
|
|
if _, err := r.Reader.DocContext(r.outputContext()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for order := range 3 {
|
|
if r.Reader.doc != nil {
|
|
for _, ref := range page.Template {
|
|
kind := ref.ZOrder
|
|
if kind == "" {
|
|
kind = "Background"
|
|
}
|
|
if layerOrder(kind) != order {
|
|
continue
|
|
}
|
|
template := r.loadTemplate(ref.TemplateID)
|
|
if template == nil {
|
|
continue
|
|
}
|
|
for templateOrder := range 3 {
|
|
if err := r.walkLayers(template.Content.Layer, templateOrder, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := r.walkLayers(page.Content.Layer, order, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.RenderAnnotations {
|
|
for _, annotation := range r.Reader.Annots[page.ID] {
|
|
if annotation.Visible != nil && !*annotation.Visible && !pageIncludesHidden(visitor) {
|
|
continue
|
|
}
|
|
if err := r.walkAnnotation(annotation, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, stamp := range r.Reader.Stamps[page.ID] {
|
|
if r.pageContext != nil {
|
|
if err := r.pageContext.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := visitor.DrawStamp(stamp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WalkObject 访问单个对象或展开复合图元,保留父级裁剪和源对象标识
|
|
// 入参: object 源对象, state 继承状态, visitor 页面访问器
|
|
// 返回: error 遍历或访问器错误
|
|
func (r *Renderer) WalkObject(object *GraphicObject, state RenderState, visitor PageVisitor) error {
|
|
return r.walkObject(object, state, visitor, nil)
|
|
}
|
|
|
|
// walkAnnotation 访问注解外观并保持独立分组和定位
|
|
// 入参: annotation 注解, visitor 页面访问器
|
|
// 返回: error 遍历或访问器错误
|
|
func (r *Renderer) walkAnnotation(annotation Annotation, visitor PageVisitor) error {
|
|
if groups, ok := visitor.(PageGroups); ok && groups.BeginAnnotation(annotation.ID) {
|
|
defer groups.EndObject()
|
|
}
|
|
box, _ := ParseBox(annotation.Appearance.Boundary)
|
|
matrix := TranslationMatrix(box.X, box.Y)
|
|
state := RenderState{Parent: &matrix, BoundaryInCTM: true, Hidden: annotation.Visible != nil && !*annotation.Visible}
|
|
for i := range annotation.Appearance.Objects {
|
|
if err := r.WalkObject(&annotation.Appearance.Objects[i], state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// walkLayers 按原顺序访问指定层级,不重新排列同类图层
|
|
// 入参: layers 图层列表, order 层级顺序, visitor 页面访问器
|
|
// 返回: error 遍历或访问器错误
|
|
func (r *Renderer) walkLayers(layers []Layer, order int, visitor PageVisitor) error {
|
|
for _, layer := range layers {
|
|
if layerOrder(layer.Type) != order {
|
|
continue
|
|
}
|
|
state := RenderState{Defaults: r.drawParamDefaults(layer.DrawParam, nil)}
|
|
if len(layer.Objects) != 0 {
|
|
for i := range layer.Objects {
|
|
if err := r.WalkObject(&layer.Objects[i], state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
for _, object := range layer.TextObject {
|
|
if err := r.WalkObject(&GraphicObject{Type: "TextObject", TextObject: object}, state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, object := range layer.PathObject {
|
|
if err := r.WalkObject(&GraphicObject{Type: "PathObject", PathObject: object}, state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, object := range layer.ImageObject {
|
|
if err := r.WalkObject(&GraphicObject{Type: "ImageObject", ImageObject: object}, state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, object := range layer.CompositeGraphicUnit {
|
|
if err := r.WalkObject(&GraphicObject{Type: "CompositeGraphicUnit", CompositeGraphicUnit: object}, state, visitor); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// walkObject 访问对象并传递当前资源引用链
|
|
// 入参: object 源对象, state 继承状态, visitor 页面访问器, references 当前资源引用链
|
|
// 返回: error 遍历或访问器错误
|
|
func (r *Renderer) walkObject(object *GraphicObject, state RenderState, visitor PageVisitor, references map[*CompositeGraphicUnit]bool) error {
|
|
if r.pageContext != nil {
|
|
if err := r.pageContext.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if pageSkipsClips(visitor) {
|
|
state.Clip = nil
|
|
}
|
|
if groups, ok := visitor.(PageGroups); ok && groups.BeginObject(object) {
|
|
defer groups.EndObject()
|
|
}
|
|
switch object.Type {
|
|
case "TextObject":
|
|
state.Hidden = state.Hidden || object.TextObject.Visible != nil && !*object.TextObject.Visible
|
|
return visitor.DrawObject(object, state)
|
|
case "PathObject":
|
|
state.Hidden = state.Hidden || object.PathObject.Visible != nil && !*object.PathObject.Visible
|
|
return visitor.DrawObject(object, state)
|
|
case "ImageObject":
|
|
state.Hidden = state.Hidden || object.ImageObject.Visible != nil && !*object.ImageObject.Visible
|
|
return visitor.DrawObject(object, state)
|
|
case "CompositeGraphicUnit", "CompositeObject":
|
|
return r.walkComposite(object.CompositeGraphicUnit, state, visitor, references)
|
|
default:
|
|
return fmt.Errorf("unsupported graphic object %q", object.Type)
|
|
}
|
|
}
|
|
|
|
// walkComposite 展开复合图元的资源、成员与透明度,不修改共享资源
|
|
// 入参: object 复合图元, state 继承状态, visitor 页面访问器, references 当前资源引用链
|
|
// 返回: error 几何或访问器错误
|
|
func (r *Renderer) walkComposite(object CompositeGraphicUnit, state RenderState, visitor PageVisitor, references map[*CompositeGraphicUnit]bool) error {
|
|
state.Hidden = state.Hidden || object.Visible != nil && !*object.Visible
|
|
if state.Hidden && !pageIncludesHidden(visitor) {
|
|
if groups, ok := visitor.(PageGroups); ok {
|
|
count, ref, seen := len(object.Objects), object.ResourceID, make(map[*CompositeGraphicUnit]bool)
|
|
for ref != "" {
|
|
unit, _ := resourceValue(r.CompositeGraphicUnits, ref)
|
|
if unit == nil || seen[unit] {
|
|
break
|
|
}
|
|
seen[unit] = true
|
|
count, ref = count+len(unit.Objects), unit.ResourceID
|
|
}
|
|
groups.SkipObjects(count)
|
|
}
|
|
return nil
|
|
}
|
|
box, _ := ParseBox(object.Boundary)
|
|
boundary := TranslationMatrix(box.X, box.Y)
|
|
if state.Parent != nil {
|
|
boundary = state.Parent.Multiply(boundary)
|
|
}
|
|
matrix := boundary.Multiply(NewMatrix(object.CTM))
|
|
if pageSkipsClips(visitor) {
|
|
state.Clip = nil
|
|
} else {
|
|
var err error
|
|
if object.Boundary != "" {
|
|
state.Clip, err = r.geometryExtentClip(box.W, box.H, boundary, state.Clip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
state.Clip, err = r.compositeExtentClip(object, matrix, state.Clip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
clips, clipMatrix := object.Clips, matrix
|
|
if !clips.usesObjectMatrix() {
|
|
clipMatrix = boundary
|
|
}
|
|
if clips != nil {
|
|
geometry, err := r.Geometry()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
state.Clip, err = geometry.Clip(r, clips, clipMatrix, state.Clip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
state.Defaults = r.drawParamDefaults(object.DrawParam, state.Defaults)
|
|
state.Parent = &matrix
|
|
if composites, ok := visitor.(PageComposites); ok {
|
|
if err := composites.VisitComposite(&object, state); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if object.ResourceID != "" {
|
|
if ref, _ := resourceValue(r.CompositeGraphicUnits, object.ResourceID); ref != nil {
|
|
if references[ref] {
|
|
return fmt.Errorf("cyclic composite resource %q", object.ResourceID)
|
|
}
|
|
if references == nil {
|
|
references = make(map[*CompositeGraphicUnit]bool)
|
|
}
|
|
references[ref] = true
|
|
copy := *ref
|
|
copy.Alpha = mergeAlpha(copy.Alpha, object.Alpha)
|
|
referenceState := state
|
|
referenceState.BoundaryInCTM = true
|
|
err := r.walkComposite(copy, referenceState, visitor, references)
|
|
delete(references, ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for member := range object.objects() {
|
|
member = mergeGraphicObjectAlpha(member, object.Alpha)
|
|
if err := r.walkObject(&member, state, visitor, references); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pageIncludesHidden 查询访问器是否需要分析隐藏内容,不改变渲染器配置
|
|
// 入参: visitor 页面访问器
|
|
// 返回: bool 是否访问隐藏内容
|
|
func pageIncludesHidden(visitor PageVisitor) bool {
|
|
analysis, ok := visitor.(PageAnalysis)
|
|
return ok && analysis.IncludeHidden()
|
|
}
|
|
|
|
// pageSkipsClips 查询访问器是否只需资源和变换,不计算几何裁剪
|
|
// 入参: visitor 页面访问器
|
|
// 返回: bool 是否跳过裁剪
|
|
func pageSkipsClips(visitor PageVisitor) bool {
|
|
analysis, ok := visitor.(PageAnalysis)
|
|
return ok && analysis.SkipClips()
|
|
}
|
|
|
|
// compositeExtentClip 按矢量资源尺寸裁剪内容,资源裁剪随实例变换
|
|
// 入参: object 矢量资源, matrix 资源到页面矩阵, parent 父裁剪
|
|
// 返回: *GeometryPath 页面裁剪, error 尺寸或几何错误
|
|
func (r *Renderer) compositeExtentClip(object CompositeGraphicUnit, matrix Matrix, parent *GeometryPath) (*GeometryPath, error) {
|
|
if !object.extentSet && object.Width == 0 && object.Height == 0 {
|
|
return parent, nil
|
|
}
|
|
return r.geometryExtentClip(object.Width, object.Height, matrix, parent)
|
|
}
|
|
|
|
// geometryExtentClip 按给定坐标系限制宽高并与父裁剪求交
|
|
// 入参: width 宽度, height 高度, matrix 到页面矩阵, parent 父裁剪
|
|
// 返回: *GeometryPath 页面裁剪, error 尺寸或几何错误
|
|
func (r *Renderer) geometryExtentClip(width, height float64, matrix Matrix, parent *GeometryPath) (*GeometryPath, error) {
|
|
if !finite(width) || !finite(height) || width < 0 || height < 0 {
|
|
return nil, fmt.Errorf("invalid clipping dimensions")
|
|
}
|
|
if width == 0 || height == 0 {
|
|
return new(GeometryPath), nil
|
|
}
|
|
geometry, err := r.Geometry()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clip, err := geometry.Transform(GeometryPath(basegeom.Rectangle(Box{W: width, H: height})), matrix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if parent != nil {
|
|
clip, err = clipGeometry(geometry, clip, parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &clip, nil
|
|
}
|