diff --git a/go.mod b/go.mod index 1663fc2..0524b95 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6 github.com/xiaoqidun/jbig2 v1.1.1 - github.com/xiaoqidun/pdfgo v0.0.0-20261004025424-ce051c731285 + github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/text v0.42.0 diff --git a/go.sum b/go.sum index 2659285..9a3b83d 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI= github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ= -github.com/xiaoqidun/pdfgo v0.0.0-20261004025424-ce051c731285 h1:KApg/AQoRuFj6UzFyNY5yOmb1di749tzYuOx9VqY0vU= -github.com/xiaoqidun/pdfgo v0.0.0-20261004025424-ce051c731285/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764 h1:YKsnM25IZ2xqdcm6Gl30boDID7ctPxNa/QPxUoCjmKQ= +github.com/xiaoqidun/pdfgo v0.0.0-20261004052854-254c40ac6764/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= diff --git a/ofdgo_annot.go b/ofdgo_annot.go index 7d57455..ef72cae 100644 --- a/ofdgo_annot.go +++ b/ofdgo_annot.go @@ -155,26 +155,16 @@ func (r *Reader) parseAnnotations(doc *Document) error { if doc.Annotations == "" { return nil } - annPath := r.ResPath(doc.Annotations) - f, err := r.openFile(annPath) + annotations, err := r.annotationIndex(doc) if err != nil { return err } - defer f.Close() - var annotations Annotations - if err := xml.NewDecoder(f).Decode(&annotations); err != nil { - return err - } if r.Annots == nil { r.Annots = make(map[string][]Annotation) } - r.annotationFiles = make(map[string][]string) + annPath := r.ResPath(doc.Annotations) for _, page := range annotations.Page { annotPath := resolveResourcePath(annPath, "", page.FileLoc) - if file, ok := r.packageFile(annotPath); ok { - annotPath = cleanPackagePath(file.Name) - } - r.annotationFiles[annotPath] = append(r.annotationFiles[annotPath], page.PageID) af, err := r.openFile(annotPath) if err != nil { continue @@ -189,3 +179,31 @@ func (r *Reader) parseAnnotations(doc *Document) error { } return nil } + +// annotationIndex 读取注解文件索引,不解析页面外观 +// 入参: doc 文档结构 +// 返回: Annotations 注解索引, error 错误信息 +func (r *Reader) annotationIndex(doc *Document) (Annotations, error) { + if doc.Annotations == "" { + return Annotations{}, nil + } + annPath := r.ResPath(doc.Annotations) + f, err := r.openFile(annPath) + if err != nil { + return Annotations{}, err + } + defer f.Close() + var annotations Annotations + if err := xml.NewDecoder(f).Decode(&annotations); err != nil { + return Annotations{}, err + } + r.annotationFiles = make(map[string][]string) + for _, page := range annotations.Page { + annotPath := resolveResourcePath(annPath, "", page.FileLoc) + if file, ok := r.packageFile(annotPath); ok { + annotPath = cleanPackagePath(file.Name) + } + r.annotationFiles[annotPath] = append(r.annotationFiles[annotPath], page.PageID) + } + return annotations, nil +} diff --git a/ofdgo_editor.go b/ofdgo_editor.go index 3e15120..aa16a9b 100644 --- a/ofdgo_editor.go +++ b/ofdgo_editor.go @@ -35,6 +35,9 @@ import ( "github.com/xiaoqidun/jbig2" ) +// editorPathValidationLimit 限制同次对象校验保留的路径数量 +const editorPathValidationLimit = 1024 + // Editor 编辑OFD文档,长度单位为毫米,页面索引从0开始,实例需串行使用 // Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo另存结果,不覆盖输入 // OnWriteProgress可选,按fonts、pages、references、resources、compress、write阶段同步回报进度,返回错误则停止保存 @@ -785,7 +788,14 @@ func (e *Editor) findObject(page int, id string) (*Layer, int, error) { // 入参: id 对象标识, object 对象内容 // 返回: GraphicObject 对象内容, error 错误信息 func (e *Editor) prepareObject(id string, object GraphicObject) (GraphicObject, error) { - object, err := (&editorValidation{Editor: e}).prepareObject(id, object) + return (&editorValidation{Editor: e}).prepareObjectCopy(id, object) +} + +// prepareObjectCopy 校验对象并隔离输入数据,同次校验共用几何缓存 +// 入参: id 对象标识, object 对象内容 +// 返回: GraphicObject 独立副本, error 错误信息 +func (v *editorValidation) prepareObjectCopy(id string, object GraphicObject) (GraphicObject, error) { + object, err := v.prepareObject(id, object) if err != nil { return GraphicObject{}, err } @@ -819,7 +829,7 @@ func (v *editorValidation) prepareObject(id string, object GraphicObject) (Graph fill, stroke = obj.FillColor, (*FillColor)(obj.StrokeColor) case "PathObject": obj := &object.PathObject - if err := creationPath(obj.AbbreviatedData); err != nil { + if err := v.creationPath(obj.AbbreviatedData); err != nil { return GraphicObject{}, err } if obj.Rule != "" && obj.Rule != "NonZero" && obj.Rule != "Even-Odd" { @@ -1185,6 +1195,23 @@ func creationPath(value string) error { return err } +// creationPath 在同次校验中复用相同路径的成功结果,不缓存错误或依赖可变资源 +// 入参: value 路径数据 +// 返回: error 错误信息 +func (v *editorValidation) creationPath(value string) error { + if v.paths[value] { + return nil + } + if err := creationPath(value); err != nil { + return err + } + if v.paths == nil || len(v.paths) >= editorPathValidationLimit { + v.paths = make(map[string]bool) + } + v.paths[value] = true + return nil +} + // creationNumbers 解析创建接口的有限十进制数值序列 // 入参: value 数值序列, count 数值数量 // 返回: []float64 数值, error 错误信息 diff --git a/ofdgo_editor_annot.go b/ofdgo_editor_annot.go index 0b8d8d6..007bfac 100644 --- a/ofdgo_editor_annot.go +++ b/ofdgo_editor_annot.go @@ -33,6 +33,9 @@ func (e *Editor) Annotations(index int) ([]AnnotationInfo, error) { if e.source == nil { return nil, nil } + if _, err := e.source.reader.Doc(); err != nil { + return nil, err + } return e.source.reader.annotationInfos(index, e.pages[index].ID), nil } diff --git a/ofdgo_editor_annot_content.go b/ofdgo_editor_annot_content.go index 1587672..0eec077 100644 --- a/ofdgo_editor_annot_content.go +++ b/ofdgo_editor_annot_content.go @@ -395,10 +395,11 @@ func (e *Editor) commitAnnotationParts(base *editorSource, parts map[string][]by if err := reader.initRoot(); err != nil { return err } - doc, err := reader.Doc() + doc, err := reader.docStructure() if err != nil { return err } + _, _ = reader.annotationIndex(doc) before, after := e.source, *base after.reader, after.document = reader, doc if before == nil { diff --git a/ofdgo_editor_batch.go b/ofdgo_editor_batch.go index c2cf1ea..8f306b2 100644 --- a/ofdgo_editor_batch.go +++ b/ofdgo_editor_batch.go @@ -90,9 +90,10 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64) origins := make([]*editorObjectOrigin, len(objects)) result := make([]string, len(objects)) maximum := e.maxID + len(objects) + validation := &editorValidation{Editor: e} for i, object := range objects { result[i] = strconv.Itoa(e.maxID + i + 1) - object, err := e.prepareCopiedObject(result[i], object) + object, err := validation.prepareCopiedObject(result[i], object) if err != nil { return nil, err } @@ -124,13 +125,18 @@ func (e *Editor) CopyObjects(page int, objects []GraphicObject, dx, dy float64) if target < len(layers) { target = len(layers) - 1 } - for i, object := range prepared { + for i := 0; i < len(prepared); { style := e.copiedLayerStyle(objects[i]) if target == len(layers) || layers[target].DrawParam != style { layers = append(layers, Layer{ID: e.nextID(), Type: "Body", DrawParam: style}) target = len(layers) - 1 } - layers[target].Objects = append(layers[target].Objects, object) + end := i + 1 + for end < len(prepared) && e.copiedLayerStyle(objects[end]) == style { + end++ + } + layers[target].Objects = append(layers[target].Objects, prepared[i:end]...) + i = end } e.replaceLayers(page, layers) return result, nil diff --git a/ofdgo_editor_color.go b/ofdgo_editor_color.go index bf0e9b3..29d796c 100644 --- a/ofdgo_editor_color.go +++ b/ofdgo_editor_color.go @@ -32,12 +32,13 @@ type PatternStyle struct { CTM *string } -// editorValidation 保存同次对象校验的底纹和矢量引用路径,允许共享但拒绝循环 +// editorValidation 保存同次对象校验的引用路径及已验证几何,允许共享但拒绝循环 type editorValidation struct { *Editor patterns map[*Pattern]bool composites map[string]bool vectors map[string]*editorCompositeNode + paths map[string]bool } // GradientStops 解析当前文档的渐变分段,保留顺序并合并透明度 diff --git a/ofdgo_editor_composite_insert.go b/ofdgo_editor_composite_insert.go index f116b3f..e45826f 100644 --- a/ofdgo_editor_composite_insert.go +++ b/ofdgo_editor_composite_insert.go @@ -64,9 +64,10 @@ func (e *Editor) CopyObjectsToComposite(page int, path ObjectPath, objects []Gra } } var content []byte + validation := &editorValidation{Editor: e} for _, source := range objects { id := e.nextID() - object, err := e.prepareCopiedObject(id, source) + object, err := validation.prepareCopiedObject(id, source) if err != nil { return err } diff --git a/ofdgo_editor_open.go b/ofdgo_editor_open.go index 82382b3..2454125 100644 --- a/ofdgo_editor_open.go +++ b/ofdgo_editor_open.go @@ -672,7 +672,8 @@ func editorPreservedObject(before, after GraphicObject) bool { // prepareCopiedObject 保真复制原对象,不重新生成字形或丢弃未修改字段 // 入参: id 新标识, object 对象快照 // 返回: GraphicObject 独立副本, error 错误信息 -func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicObject, error) { +func (v *editorValidation) prepareCopiedObject(id string, object GraphicObject) (GraphicObject, error) { + e := v.Editor origin := e.snapshotOrigin(object) if origin == nil && object.origin != nil { references := make(map[string]bool) @@ -684,7 +685,7 @@ func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicOb } object.origin = nil if origin == nil { - return e.prepareObject(id, object) + return v.prepareObjectCopy(id, object) } if !editorXMLTransformable(origin.node) || !editorXMLContainersSupported(origin.node) || !editorXMLCopyable(origin.node) || origin.reason != nil && (object.Type != "PathObject" || editReason(origin.reason) != EditUnsupportedColor) { return GraphicObject{}, fmt.Errorf("object cannot be copied without changing its original structure") @@ -704,7 +705,7 @@ func (e *Editor) prepareCopiedObject(id string, object GraphicObject) (GraphicOb if !editorXMLSupported(origin.node) { return GraphicObject{}, fmt.Errorf("copy would replace unsupported object content") } - return e.prepareObject(id, object) + return v.prepareObjectCopy(id, object) } if err := validateEditorGeometry(object); err != nil { return GraphicObject{}, err diff --git a/ofdgo_editor_package.go b/ofdgo_editor_package.go index bc7a4af..f56cbb9 100644 --- a/ofdgo_editor_package.go +++ b/ofdgo_editor_package.go @@ -391,6 +391,18 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err // 入参: layer 图层 // 返回: []byte 图层XML, error 错误信息 func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) { + if !slices.ContainsFunc(layer.Objects, func(object GraphicObject) bool { return e.objectOrigin(editorObjectID(object)) != nil }) { + data, err := encodeOFDXML(func(x *ofdXML) { + attrs := ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: layer.ID}, {Name: xml.Name{Local: "Type"}, Value: layer.Type}} + attrs.add("DrawParam", layer.DrawParam) + x.root("Layer", attrs) + for _, object := range layer.Objects { + x.object(object, false) + } + x.end("Layer") + }) + return bytes.TrimPrefix(data, []byte(xml.Header)), err + } var content []byte for _, object := range layer.Objects { var data []byte @@ -417,6 +429,17 @@ func (e *Editor) sourceLayerXML(layer Layer) ([]byte, error) { // 入参: page 页面 // 返回: []byte 页面XML, error 错误信息 func (e *Editor) sourceNewPageXML(page PageContent) ([]byte, error) { + preserved := false + for _, layer := range page.Content.Layer { + if slices.ContainsFunc(layer.Objects, func(object GraphicObject) bool { return e.objectOrigin(editorObjectID(object)) != nil }) { + preserved = true + break + } + } + if !preserved { + data, err := encodeOFDXML(func(x *ofdXML) { x.page(page) }) + return bytes.TrimPrefix(data, []byte(xml.Header)), err + } area, err := editorXMLContainer("Area", nil, editorXMLText("PhysicalBox", page.Area.PhysicalBox)) if err != nil { return nil, err @@ -433,7 +456,15 @@ func (e *Editor) sourceNewPageXML(page PageContent) ([]byte, error) { if err != nil { return nil, err } - return editorXMLContainer("Page", nil, append(area, content...)) + data := append(area, content...) + if len(page.Actions) != 0 { + actions, err := encodeOFDXML(func(x *ofdXML) { x.actions(page.Actions) }) + if err != nil { + return nil, err + } + data = append(data, bytes.TrimPrefix(actions, []byte(xml.Header))...) + } + return editorXMLContainer("Page", nil, data) } // writeSource 将未修改ZIP条目直接复制到新包,逐项写入改动,不持有原资源解压副本 @@ -585,6 +616,9 @@ func (e *Editor) sourceReader(progress editorProgress) (*Reader, error) { delete(reader.pageHeaderCache, name) } } + if _, err := reader.Doc(); err != nil { + return nil, err + } return reader, nil } files := maps.Clone(e.source.reader.files) diff --git a/ofdgo_font_backend.go b/ofdgo_font_backend.go index 23c8ec4..2f20d2c 100644 --- a/ofdgo_font_backend.go +++ b/ofdgo_font_backend.go @@ -45,6 +45,13 @@ type FontOutlineBatch interface { GlyphOutlines(glyphs []uint16, size float64) ([]GeometryPath, error) } +// FontGlyphBounds 提供与字形轮廓一致的边界,字号及边界单位为毫米 +// 不使用字体全局边界替代单个字形,空轮廓返回零边界 +type FontGlyphBounds interface { + FontOutlines + GlyphBounds(glyph uint16, size float64) (Box, error) +} + // FontGlyphDiagnostics 提供已提取字形的恢复提示,不将提示作为轮廓解析失败 // 字形不存在或不支持的指令仍由GlyphOutline返回错误 type FontGlyphDiagnostics interface { diff --git a/ofdgo_pdf_link.go b/ofdgo_pdf_link.go index 8b09add..4a38dc6 100644 --- a/ofdgo_pdf_link.go +++ b/ofdgo_pdf_link.go @@ -27,18 +27,24 @@ import ( // 入参: ctx 取消上下文, page PDF页面, strict 严格检查开关 // 返回: error 错误信息 func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict bool) error { - annotations, err := page.Annotations() + annotations, err := page.AnnotationsContext(ctx) if err != nil { return err } parents := make(map[pdfgo.Reference]int) for i, annotation := range annotations { + if err := ctx.Err(); err != nil { + return err + } if annotation.Reference != (pdfgo.Reference{}) && annotation.Subtype != "Popup" { parents[annotation.Reference] = i } } attached := make(map[int]bool) for i, annotation := range annotations { + if err := ctx.Err(); err != nil { + return err + } if annotation.Subtype != "Popup" { continue } @@ -61,6 +67,9 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict } } for i, annotation := range annotations { + if err := ctx.Err(); err != nil { + return err + } if attached[i] { continue } diff --git a/ofdgo_pdf_object.go b/ofdgo_pdf_object.go index f33adbf..cc36ac5 100644 --- a/ofdgo_pdf_object.go +++ b/ofdgo_pdf_object.go @@ -313,8 +313,11 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error { var glyphPaths []pdfgo.Path const unit = 25.4 / 72 m := p.matrix.Mul(mark.Matrix).Mul(pdfgo.Matrix{1 / unit, 0, 0, -1 / unit, 0, 0}) - var points []pdfgo.Point - object := TextObject{Font: id, Size: mark.Size * unit, HScale: mark.HorizontalScale} + points := make([]pdfgo.Point, 0, 4*len(mark.Glyphs)) + object := TextObject{Font: id, Size: mark.Size * unit, HScale: mark.HorizontalScale, TextCode: make([]TextCode, 0, len(mark.Glyphs))} + if embedded { + object.CGTransform = make([]CGTransform, 0, len(mark.Glyphs)) + } position := 0 for n, glyph := range mark.Glyphs { if err := p.ctx.Err(); err != nil { @@ -360,7 +363,17 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error { position += count } if embedded || outlineStroke { - path, err := outline.GlyphOutline(gid, object.Size) + var path GeometryPath + var bounds Box + var err error + if provider, ok := outline.(FontGlyphBounds); ok && !outlineStroke { + bounds, err = provider.GlyphBounds(gid, object.Size) + } else { + path, err = outline.GlyphOutline(gid, object.Size) + if err == nil { + bounds, err = path.Bounds() + } + } if err != nil { return fmt.Errorf("PDF font %s outline: %w", font.Name, err) } @@ -378,10 +391,6 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error { } } } - bounds, err := path.Bounds() - if err != nil { - return err - } for _, point := range []pdfgo.Point{{X: bounds.X, Y: bounds.Y}, {X: bounds.X + bounds.W, Y: bounds.Y}, {X: bounds.X, Y: bounds.Y + bounds.H}, {X: bounds.X + bounds.W, Y: bounds.Y + bounds.H}} { points = append(points, m.Apply(pdfgo.Point{X: origin.X*unit + point.X*mark.HorizontalScale, Y: -origin.Y*unit + point.Y})) } diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 37fdbba..0c290cf 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -46,6 +46,7 @@ type Reader struct { compositeGraphicUnitCache map[string]*CompositeGraphicUnit pageHeaderCache map[string]PageContent doc *Document + relatedPending bool documentIndex int Stamps map[string][]Stamp Annots map[string][]Annotation @@ -128,6 +129,21 @@ func (r *Reader) Document(index int) (*Reader, error) { // Doc 获取当前文档结构 // 返回: *Document 文档结构, error 错误信息 func (r *Reader) Doc() (*Document, error) { + doc, err := r.docStructure() + if err != nil { + return nil, err + } + if r.relatedPending { + r.relatedPending = false + _ = r.parseAnnotations(doc) + _ = r.parseSignatures(doc) + } + return doc, nil +} + +// docStructure 读取文档及资源索引,关联注解和签名由Doc按需解析 +// 返回: *Document 文档结构, error 错误信息 +func (r *Reader) docStructure() (*Document, error) { if r.doc != nil { return r.doc, nil } @@ -155,8 +171,7 @@ func (r *Reader) Doc() (*Document, error) { r.loadRes(res) } r.doc = &doc - _ = r.parseAnnotations(&doc) - _ = r.parseSignatures(&doc) + r.relatedPending = true return r.doc, nil } diff --git a/ofdgo_renderer_font_canvas.go b/ofdgo_renderer_font_canvas.go index 583bd6c..1eec533 100644 --- a/ofdgo_renderer_font_canvas.go +++ b/ofdgo_renderer_font_canvas.go @@ -16,16 +16,33 @@ package ofdgo import ( "fmt" + "sync" "github.com/tdewolff/canvas" "github.com/tdewolff/font" ) +// canvasGlyphBoundsLimit 限制单个字体保留的字形与字号边界数量 +const canvasGlyphBoundsLimit = 4096 + // canvasFontMetrics 保留默认字体度量,写出时不更新原始时间 type canvasFontMetrics struct { *font.SFNT *fontShaper outlines *sfntOutliner + bounds *canvasFontBounds +} + +// canvasFontBounds 缓存已成功提取的字形边界,不保留轮廓或失败结果 +type canvasFontBounds struct { + mu sync.Mutex + values map[canvasGlyphSize]Box +} + +// canvasGlyphSize 区分字形编号及精确毫米字号 +type canvasGlyphSize struct { + glyph uint16 + size float64 } // ResolveFont 按需加载字体资源,保留内嵌数据及外部字体的名称、样式和集合索引匹配 @@ -43,7 +60,7 @@ func (CanvasBackend) OpenFont(data []byte) (FontMetrics, error) { if err != nil { return nil, err } - metrics := &canvasFontMetrics{SFNT: sfnt, outlines: &sfntOutliner{font: sfnt}} + metrics := &canvasFontMetrics{SFNT: sfnt, outlines: &sfntOutliner{font: sfnt}, bounds: &canvasFontBounds{}} metrics.fontShaper = &fontShaper{metrics: metrics} return metrics, nil } @@ -68,6 +85,38 @@ func (f canvasFontMetrics) GlyphOutline(glyph uint16, size float64) (GeometryPat return *geometryFromCanvasPath(path), nil } +// GlyphBounds 返回字形轮廓的精确边界,重复编号及字号复用有限缓存 +// 入参: glyph 字形编号, size 字号,单位为毫米 +// 返回: Box 字形边界, error 字形解析错误 +func (f canvasFontMetrics) GlyphBounds(glyph uint16, size float64) (Box, error) { + key := canvasGlyphSize{glyph: glyph, size: size} + if f.bounds != nil { + f.bounds.mu.Lock() + value, ok := f.bounds.values[key] + f.bounds.mu.Unlock() + if ok { + return value, nil + } + } + outline, err := f.GlyphOutline(glyph, size) + if err != nil { + return Box{}, err + } + value, err := outline.Bounds() + if err != nil { + return Box{}, err + } + if f.bounds != nil { + f.bounds.mu.Lock() + if f.bounds.values == nil || len(f.bounds.values) >= canvasGlyphBoundsLimit { + f.bounds.values = make(map[canvasGlyphSize]Box) + } + f.bounds.values[key] = value + f.bounds.mu.Unlock() + } + return value, nil +} + // GlyphWarning 返回字形指令栈下溢后保留设计轮廓的提示,原始字体不变 // 入参: glyph 字形编号 // 返回: error 恢复原因,无恢复时为空 diff --git a/ofdgo_writer.go b/ofdgo_writer.go index 4f63d74..d2fdc51 100644 --- a/ofdgo_writer.go +++ b/ofdgo_writer.go @@ -517,6 +517,7 @@ func (x *ofdXML) layer(layer Layer) { var attrs ofdAttrs attrs.add("ID", layer.ID) attrs.add("Type", layer.Type) + attrs.add("DrawParam", layer.DrawParam) x.start("Layer", attrs) for _, object := range layer.Objects { x.object(object, false)