mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
778d36d373
commit
4b9dbd6447
6 files changed
+520
-164
No files matched your search
+102
@@ -18,6 +18,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Annotations 注释集合
|
// Annotations 注释集合
|
||||||
@@ -93,6 +95,106 @@ type Appearance struct {
|
|||||||
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析注释入口,按命名空间读取直接页面引用
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (a *Annotations) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
if start.Name.Local != "Annotations" {
|
||||||
|
return fmt.Errorf("expected Annotations root, got %s", start.Name.Local)
|
||||||
|
}
|
||||||
|
*a = Annotations{XMLName: start.Name}
|
||||||
|
return decodeOFDCollection(d, start, "Page", &a.Page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 读取注释所属页面及文件路径,忽略扩展中的同名字段
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (page *AnnPage) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*page = AnnPage{PageID: attrValue(start, "PageID")}
|
||||||
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
if node.Name.Local == "FileLoc" {
|
||||||
|
return d.DecodeElement(&page.FileLoc, &node)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析分页注释文件,不将外部同名节点作为注释
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (page *PageAnnot) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
if start.Name.Local != "PageAnnot" {
|
||||||
|
return fmt.Errorf("expected PageAnnot root, got %s", start.Name.Local)
|
||||||
|
}
|
||||||
|
*page = PageAnnot{XMLName: start.Name}
|
||||||
|
return decodeOFDCollection(d, start, "Annot", &page.Annot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析注释属性、说明、参数和外观,保留未指定可见性的默认语义
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (a *Annotation) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*a = Annotation{
|
||||||
|
ID: attrValue(start, "ID"), Type: attrValue(start, "Type"), Subtype: attrValue(start, "Subtype"),
|
||||||
|
Creator: attrValue(start, "Creator"), LastModDate: attrValue(start, "LastModDate"),
|
||||||
|
}
|
||||||
|
for _, attr := range start.Attr {
|
||||||
|
if attr.Name.Space != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var target *bool
|
||||||
|
switch attr.Name.Local {
|
||||||
|
case "Visible":
|
||||||
|
a.Visible = new(bool)
|
||||||
|
target = a.Visible
|
||||||
|
case "NoZoom":
|
||||||
|
target = &a.NoZoom
|
||||||
|
case "NoRotate":
|
||||||
|
target = &a.NoRotate
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, err := strconv.ParseBool(strings.TrimSpace(attr.Value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*target = value
|
||||||
|
}
|
||||||
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
switch node.Name.Local {
|
||||||
|
case "Remark":
|
||||||
|
return d.DecodeElement(&a.Remark, &node)
|
||||||
|
case "Parameters":
|
||||||
|
return d.DecodeElement(&a.Parameters, &node)
|
||||||
|
case "Appearance":
|
||||||
|
return d.DecodeElement(&a.Appearance, &node)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 读取标准注释参数,保留参数顺序和重复名称
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (p *AnnotationParameters) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*p = AnnotationParameters{}
|
||||||
|
return decodeOFDCollection(d, start, "Parameter", &p.Parameter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 读取参数名称与文本,不采用带命名空间的名称属性
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解析错误
|
||||||
|
func (p *AnnotationParameter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type plain AnnotationParameter
|
||||||
|
var value plain
|
||||||
|
start = ofdXMLStart(start)
|
||||||
|
if err := d.DecodeElement(&value, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = AnnotationParameter(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AnnotationInfos 按页面顺序获取注释信息,包含不可见注释
|
// AnnotationInfos 按页面顺序获取注释信息,包含不可见注释
|
||||||
// 返回: []AnnotationInfo 注释信息, error 错误信息
|
// 返回: []AnnotationInfo 注释信息, error 错误信息
|
||||||
func (r *Reader) AnnotationInfos() ([]AnnotationInfo, error) {
|
func (r *Reader) AnnotationInfos() ([]AnnotationInfo, error) {
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func (r *Reader) compressionImageSizes(ctx context.Context, images []ImageInfo,
|
|||||||
return result, ctx.Err()
|
return result, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawObject 汇总图片、替换图及文字和路径底纹的尺寸,不使用裁剪后的局部面积
|
// DrawObject 汇总图片、替换图及文字、路径和图像边框底纹的尺寸,不使用裁剪后的局部面积
|
||||||
// 入参: object 图形对象, state 继承状态
|
// 入参: object 图形对象, state 继承状态
|
||||||
// 返回: error 取消或几何错误
|
// 返回: error 取消或几何错误
|
||||||
func (v *compressionImages) DrawObject(object *GraphicObject, state RenderState) error {
|
func (v *compressionImages) DrawObject(object *GraphicObject, state RenderState) error {
|
||||||
@@ -193,6 +193,12 @@ func (v *compressionImages) DrawObject(object *GraphicObject, state RenderState)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if border := img.Border; border != nil && border.BorderColor != nil && border.BorderColor.Pattern != nil {
|
||||||
|
matrix, _ := renderObjectMatrix(img.Boundary, IdentityMatrix, state)
|
||||||
|
if err := v.patternImages((*FillColor)(border.BorderColor), matrix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
local := NewMatrix(img.CTM)
|
local := NewMatrix(img.CTM)
|
||||||
if img.CTM == "" {
|
if img.CTM == "" {
|
||||||
local = Matrix{a: box.W, d: box.H}
|
local = Matrix{a: box.W, d: box.H}
|
||||||
|
|||||||
+191
-82
@@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -57,6 +58,22 @@ type FontProcessOptions struct {
|
|||||||
OnProgress func(stage string, completed, total int) error
|
OnProgress func(stage string, completed, total int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// editorFontChange 汇总同一文档的字体修改,XML偏移均基于修改前的文件
|
||||||
|
type editorFontChange struct {
|
||||||
|
patches map[string][]editorXMLPatch
|
||||||
|
files map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// editorBindingFontCache 仅保留当前文档最近读取的字体文件及最近解析结果,操作结束后释放
|
||||||
|
type editorBindingFontCache struct {
|
||||||
|
source string
|
||||||
|
data []byte
|
||||||
|
checksum [32]byte
|
||||||
|
name string
|
||||||
|
index int
|
||||||
|
font *FontResource
|
||||||
|
}
|
||||||
|
|
||||||
// EmbedFont 嵌入当前文档指定字体,保留资源标识及全部使用位置,成功后可撤销
|
// EmbedFont 嵌入当前文档指定字体,保留资源标识及全部使用位置,成功后可撤销
|
||||||
// file为空时从编辑器字体来源精确匹配;显式文件表示调用方确认替换,仍校验字符和字形
|
// file为空时从编辑器字体来源精确匹配;显式文件表示调用方确认替换,仍校验字符和字形
|
||||||
// 入参: ctx 取消上下文, id 字体标识, file 字体文件, index 集合索引
|
// 入参: ctx 取消上下文, id 字体标识, file 字体文件, index 集合索引
|
||||||
@@ -149,6 +166,7 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pending := &editorFontChange{patches: make(map[string][]editorXMLPatch), files: make(map[string][]byte)}
|
||||||
ids := slices.Sorted(maps.Keys(scan.fonts))
|
ids := slices.Sorted(maps.Keys(scan.fonts))
|
||||||
ids = slices.DeleteFunc(ids, func(id string) bool {
|
ids = slices.DeleteFunc(ids, func(id string) bool {
|
||||||
return (mode == FontEmbed) == (scan.fonts[id].location != "")
|
return (mode == FontEmbed) == (scan.fonts[id].location != "")
|
||||||
@@ -166,13 +184,20 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
item := FontChange{Document: document, ID: id, Name: declaration.font.FontName}
|
item := FontChange{Document: document, ID: id, Name: declaration.font.FontName}
|
||||||
item.Changed, err = e.changeFontSnapshot(ctx, scan, id, mode, FontFile{}, 0)
|
change, err := e.prepareFontBinding(ctx, scan, id, mode, FontFile{}, 0, pending.files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
item.Reason = err.Error()
|
item.Reason = err.Error()
|
||||||
}
|
}
|
||||||
|
item.Changed = change != nil
|
||||||
|
if change != nil {
|
||||||
|
for name, patches := range change.patches {
|
||||||
|
pending.patches[name] = append(pending.patches[name], patches...)
|
||||||
|
}
|
||||||
|
maps.Copy(pending.files, change.files)
|
||||||
|
}
|
||||||
report = append(report, item)
|
report = append(report, item)
|
||||||
if err := progress("fonts", len(report), total); err != nil {
|
if err := progress("fonts", len(report), total); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -182,12 +207,11 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
|
|||||||
if mode == FontExternal {
|
if mode == FontExternal {
|
||||||
externalFiles = append(externalFiles, declaration.location)
|
externalFiles = append(externalFiles, declaration.location)
|
||||||
}
|
}
|
||||||
scan, err = scanEditorFonts(ctx, reader, document)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := pending.apply(ctx, scan); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := progress("fonts", len(report), len(report)); err != nil {
|
if err := progress("fonts", len(report), len(report)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -208,26 +232,40 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
|
|||||||
// 入参: ctx 取消上下文, scan 文档引用, id 字体标识, mode 保存方式, file 字体文件, index 集合索引
|
// 入参: ctx 取消上下文, scan 文档引用, id 字体标识, mode 保存方式, file 字体文件, index 集合索引
|
||||||
// 返回: bool 是否修改, error 字体或结构错误
|
// 返回: bool 是否修改, error 字体或结构错误
|
||||||
func (e *Editor) changeFontSnapshot(ctx context.Context, scan *editorFontDocument, id string, mode FontMode, file FontFile, index int) (bool, error) {
|
func (e *Editor) changeFontSnapshot(ctx context.Context, scan *editorFontDocument, id string, mode FontMode, file FontFile, index int) (bool, error) {
|
||||||
|
change, err := e.prepareFontBinding(ctx, scan, id, mode, file, index, nil)
|
||||||
|
if err != nil || change == nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := change.apply(ctx, scan); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareFontBinding 校验单个字体并生成独立修改集,不改变文档及已准备的资源
|
||||||
|
// 入参: ctx 取消上下文, scan 文档引用, id 字体标识, mode 保存方式, file 字体文件, index 集合索引, pending 已准备的资源文件
|
||||||
|
// 返回: *editorFontChange 字体修改,无需处理时为nil, error 字体或结构错误
|
||||||
|
func (e *Editor) prepareFontBinding(ctx context.Context, scan *editorFontDocument, id string, mode FontMode, file FontFile, index int, pending map[string][]byte) (*editorFontChange, error) {
|
||||||
declaration := scan.fonts[editorResourceID(id)]
|
declaration := scan.fonts[editorResourceID(id)]
|
||||||
if declaration == nil {
|
if declaration == nil {
|
||||||
return false, fmt.Errorf("font not found: %s", id)
|
return nil, fmt.Errorf("font not found: %s", id)
|
||||||
}
|
}
|
||||||
if (mode == FontEmbed) == (declaration.location != "") {
|
if (mode == FontEmbed) == (declaration.location != "") {
|
||||||
return false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if scan.unsafe {
|
if scan.unsafe {
|
||||||
return false, fmt.Errorf("font references include unsupported content")
|
return nil, fmt.Errorf("font references include unsupported content")
|
||||||
}
|
}
|
||||||
if e.backends.FontResources == nil {
|
if e.backends.FontResources == nil {
|
||||||
return false, ErrBackendUnavailable
|
return nil, ErrBackendUnavailable
|
||||||
}
|
}
|
||||||
var data []byte
|
var data []byte
|
||||||
var err error
|
var err error
|
||||||
explicit := mode == FontEmbed && len(file.Data) != 0
|
explicit := mode == FontEmbed && len(file.Data) != 0
|
||||||
if mode == FontExternal {
|
if mode == FontExternal {
|
||||||
data, err = scan.reader.readFile(declaration.location)
|
data, err = scan.fontCache.read(ctx, scan.reader, declaration.location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file = FontFile{Data: data}
|
file = FontFile{Data: data}
|
||||||
if bytes.HasPrefix(data, []byte("ttcf")) {
|
if bytes.HasPrefix(data, []byte("ttcf")) {
|
||||||
@@ -236,136 +274,207 @@ func (e *Editor) changeFontSnapshot(ctx context.Context, scan *editorFontDocumen
|
|||||||
} else if len(file.Data) == 0 {
|
} else if len(file.Data) == 0 {
|
||||||
file, err = e.resolveBindingFont(scan, declaration)
|
file, err = e.resolveBindingFont(scan, declaration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
index = 0
|
index = 0
|
||||||
}
|
}
|
||||||
parsed, err := e.backends.FontResources.OpenFontResource(file, index)
|
parsed, err := scan.fontCache.open(ctx, e.backends.FontResources, file, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if mode == FontEmbed {
|
if mode == FontEmbed {
|
||||||
if err := validateFontEmbedding(parsed.Data); err != nil {
|
if err := validateFontEmbedding(parsed.Data); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changes := make(map[string][]byte)
|
change := &editorFontChange{patches: make(map[string][]editorXMLPatch), files: make(map[string][]byte)}
|
||||||
verifiedGlyphs := false
|
verifiedGlyphs := false
|
||||||
for _, name := range slices.Sorted(maps.Keys(scan.files)) {
|
for _, text := range scan.texts[editorResourceID(id)] {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file := scan.files[name]
|
file, node := text.file, text.node
|
||||||
var patches []editorXMLPatch
|
var object TextObject
|
||||||
for _, node := range file.texts {
|
if err := xml.Unmarshal(file.data[node.start:node.end], &object); err != nil {
|
||||||
if editorResourceID(node.attr("Font")) != editorResourceID(id) {
|
return nil, err
|
||||||
continue
|
}
|
||||||
|
if err := checkFontBinding(object, parsed, mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if explicit && len(object.CGTransform) != 0 && !verifiedGlyphs {
|
||||||
|
source, err := e.resolveBindingFont(scan, declaration)
|
||||||
|
if err != nil || !sameBindingFont(source.Data, parsed.Data) {
|
||||||
|
return nil, fmt.Errorf("font glyph mapping requires the original font")
|
||||||
}
|
}
|
||||||
var object TextObject
|
verifiedGlyphs = true
|
||||||
if err := xml.Unmarshal(file.data[node.start:node.end], &object); err != nil {
|
}
|
||||||
return false, err
|
if mode == FontExternal {
|
||||||
}
|
for _, child := range node.children {
|
||||||
if err := checkFontBinding(object, parsed, mode); err != nil {
|
if child.matchesOFD("CGTransform") {
|
||||||
return false, err
|
change.patches[file.name] = append(change.patches[file.name], editorXMLPatch{start: child.start, end: child.end})
|
||||||
}
|
|
||||||
if explicit && len(object.CGTransform) != 0 && !verifiedGlyphs {
|
|
||||||
source, err := e.resolveBindingFont(scan, declaration)
|
|
||||||
if err != nil || !sameBindingFont(source.Data, parsed.Data) {
|
|
||||||
return false, fmt.Errorf("font glyph mapping requires the original font")
|
|
||||||
}
|
|
||||||
verifiedGlyphs = true
|
|
||||||
}
|
|
||||||
if mode == FontExternal {
|
|
||||||
for _, child := range node.children {
|
|
||||||
if child.matchesOFD("CGTransform") {
|
|
||||||
patches = append(patches, editorXMLPatch{start: child.start, end: child.end})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(patches) != 0 {
|
|
||||||
changes[name] = editorPatchXML(file.data, patches)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data = declaration.file.data
|
data = declaration.file.data
|
||||||
node := declaration.node
|
node := declaration.node
|
||||||
if modified, ok := changes[declaration.file.name]; ok {
|
|
||||||
data = modified
|
|
||||||
node, err = fontBindingNode(data, id)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fontFile := node.child("FontFile")
|
fontFile := node.child("FontFile")
|
||||||
|
var fragment []byte
|
||||||
if mode == FontEmbed {
|
if mode == FontEmbed {
|
||||||
digest := sha256.Sum256(parsed.Data)
|
digest := sha256.Sum256(parsed.Data)
|
||||||
name := fmt.Sprintf("Fonts/%x%s", digest, parsed.Extension)
|
name, reused, err := fontBindingFile(ctx, scan.reader, pending, fmt.Sprintf("Fonts/%x%s", digest, parsed.Extension), parsed.Data)
|
||||||
if existing, err := scan.reader.readFile(name); err != nil || !bytes.Equal(existing, parsed.Data) {
|
if err != nil {
|
||||||
name = packageAvailableName(scan.reader, changes, name)
|
return nil, err
|
||||||
changes[name] = bytes.Clone(parsed.Data)
|
}
|
||||||
|
if !reused {
|
||||||
|
change.files[name] = bytes.Clone(parsed.Data)
|
||||||
}
|
}
|
||||||
content := node.textXML("FontFile", "/"+name)
|
content := node.textXML("FontFile", "/"+name)
|
||||||
if fontFile != nil {
|
if fontFile != nil {
|
||||||
data = editorPatchXML(data, []editorXMLPatch{{fontFile.start, fontFile.end, content}})
|
fragment = editorPatchXML(data[node.start:node.end], []editorXMLPatch{{fontFile.start - node.start, fontFile.end - node.start, content}})
|
||||||
} else if node.open == node.end {
|
} else if node.open == node.end {
|
||||||
data = editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, node, content)})
|
patch := editorXMLContent(data, node, content)
|
||||||
|
patch.start -= node.start
|
||||||
|
patch.end -= node.start
|
||||||
|
fragment = editorPatchXML(data[node.start:node.end], []editorXMLPatch{patch})
|
||||||
} else {
|
} else {
|
||||||
data = editorPatchXML(data, []editorXMLPatch{{node.close, node.close, content}})
|
fragment = editorPatchXML(data[node.start:node.end], []editorXMLPatch{{node.close - node.start, node.close - node.start, content}})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fontFile == nil {
|
if fontFile == nil {
|
||||||
return false, fmt.Errorf("font file declaration is missing")
|
return nil, fmt.Errorf("font file declaration is missing")
|
||||||
}
|
}
|
||||||
fragment := editorPatchXML(data[node.start:node.end], []editorXMLPatch{{fontFile.start - node.start, fontFile.end - node.start, nil}})
|
fragment = editorPatchXML(data[node.start:node.end], []editorXMLPatch{{fontFile.start - node.start, fontFile.end - node.start, nil}})
|
||||||
for _, field := range []string{"FontName", "FamilyName"} {
|
for _, field := range []string{"FontName", "FamilyName"} {
|
||||||
root, err := parseEditorXML(fragment)
|
root, err := parseEditorXML(fragment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
value := root.attr(field)
|
value := root.attr(field)
|
||||||
if stripped := externalFontName(value); stripped != value {
|
if stripped := externalFontName(value); stripped != value {
|
||||||
fragment, err = editorXMLAttribute(fragment, root, field, stripped)
|
fragment, err = editorXMLAttribute(fragment, root, field, stripped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data = editorPatchXML(data, []editorXMLPatch{{node.start, node.end, fragment}})
|
|
||||||
}
|
}
|
||||||
if explicit {
|
if explicit {
|
||||||
for _, field := range []struct{ name, value string }{{"FontName", parsed.Font.FontName}, {"FamilyName", parsed.Font.FamilyName}, {"Charset", "unicode"}} {
|
for _, field := range []struct{ name, value string }{{"FontName", parsed.Font.FontName}, {"FamilyName", parsed.Font.FamilyName}, {"Charset", "unicode"}} {
|
||||||
node, err := fontBindingNode(data, id)
|
root, err := parseEditorXML(fragment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fragment, err := editorXMLAttribute(data, node, field.name, field.value)
|
fragment, err = editorXMLAttribute(fragment, root, field.name, field.value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data = editorPatchXML(data, []editorXMLPatch{{node.start, node.end, fragment}})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changes[declaration.file.name] = data
|
if err := ctx.Err(); err != nil {
|
||||||
if err := scan.apply(ctx, changes); err != nil {
|
return nil, err
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
return true, nil
|
change.patches[declaration.file.name] = append(change.patches[declaration.file.name], editorXMLPatch{node.start, node.end, fragment})
|
||||||
|
return change, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontBindingNode 在修改后的资源XML中重新定位字体声明
|
// apply 合并同一文档的XML修改,再统一隔离其他文档及版本使用的文件
|
||||||
// 入参: data 资源XML, id 字体标识
|
// 入参: ctx 取消上下文, scan 修改前的文档引用
|
||||||
// 返回: *editorXML 字体节点, error 解析或查找错误
|
// 返回: error 取消、引用或结构错误
|
||||||
func fontBindingNode(data []byte, id string) (*editorXML, error) {
|
func (c *editorFontChange) apply(ctx context.Context, scan *editorFontDocument) error {
|
||||||
root, err := parseEditorXML(data)
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(c.patches) == 0 && len(c.files) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
changes := maps.Clone(c.files)
|
||||||
|
for name, patches := range c.patches {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
changes[name] = editorPatchXML(scan.files[name].data, patches)
|
||||||
|
}
|
||||||
|
return scan.apply(ctx, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read 复用当前文档最近读取的字体,避免共享引用重复解压,不缓存错误或取消结果
|
||||||
|
// 入参: ctx 取消上下文, reader 包快照, name 字体路径
|
||||||
|
// 返回: []byte 只读字体数据, error 读取或取消错误
|
||||||
|
func (c *editorBindingFontCache) read(ctx context.Context, reader *Reader, name string) ([]byte, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name = cleanPackagePath(name)
|
||||||
|
if c.source == name && c.data != nil {
|
||||||
|
return c.data, nil
|
||||||
|
}
|
||||||
|
c.source, c.data = "", nil
|
||||||
|
data, err := reader.readFileViewContext(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for node := range editorResourceNodes(root, "Fonts", "Font") {
|
if err := ctx.Err(); err != nil {
|
||||||
if editorResourceID(node.attr("ID")) == editorResourceID(id) {
|
return nil, err
|
||||||
return node, nil
|
}
|
||||||
|
c.source, c.data = name, data
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// open 按字体内容、名称及集合索引复用最近的解析结果,不缓存错误或取消结果
|
||||||
|
// 入参: ctx 取消上下文, backend 字体资源后端, file 字体文件, index 集合索引
|
||||||
|
// 返回: *FontResource 只读字体资源, error 解析或取消错误
|
||||||
|
func (c *editorBindingFontCache) open(ctx context.Context, backend FontResourceBackend, file FontFile, index int) (*FontResource, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
checksum := sha256.Sum256(file.Data)
|
||||||
|
if c.font != nil && c.checksum == checksum && c.name == file.Name && c.index == index {
|
||||||
|
return c.font, ctx.Err()
|
||||||
|
}
|
||||||
|
c.font = nil
|
||||||
|
font, err := backend.OpenFontResource(file, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.checksum, c.name, c.index, c.font = checksum, file.Name, index, font
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontBindingFile 查找未占用的字体路径,同名内容一致时复用已有文件或本次待写资源
|
||||||
|
// 入参: ctx 取消上下文, reader 包快照, pending 待写资源, name 期望路径, data 字体内容
|
||||||
|
// 返回: string 包内路径, bool 是否复用, error 取消错误
|
||||||
|
func fontBindingFile(ctx context.Context, reader *Reader, pending map[string][]byte, name string, data []byte) (string, bool, error) {
|
||||||
|
base, extension := strings.TrimSuffix(name, path.Ext(name)), path.Ext(name)
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
candidate := name
|
||||||
|
if i != 0 {
|
||||||
|
candidate = base + "_" + strconv.Itoa(i) + extension
|
||||||
|
}
|
||||||
|
existing, occupied := pending[candidate]
|
||||||
|
if !occupied {
|
||||||
|
existing, occupied = reader.files[candidate]
|
||||||
|
if !occupied {
|
||||||
|
_, occupied = reader.packageFile(candidate)
|
||||||
|
if occupied {
|
||||||
|
var err error
|
||||||
|
existing, err = reader.readFileViewContext(ctx, candidate)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !occupied || bytes.Equal(existing, data) {
|
||||||
|
return candidate, occupied, ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("font declaration is missing")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sameBindingFont 比较独立字体的全部数据表,忽略重新封装产生的校验和差异
|
// sameBindingFont 比较独立字体的全部数据表,忽略重新封装产生的校验和差异
|
||||||
|
|||||||
+20
-13
@@ -26,19 +26,25 @@ import (
|
|||||||
|
|
||||||
// editorFontDocument 保存一个文档的字体声明与完整内容引用,不进入附件和签名封装
|
// editorFontDocument 保存一个文档的字体声明与完整内容引用,不进入附件和签名封装
|
||||||
type editorFontDocument struct {
|
type editorFontDocument struct {
|
||||||
reader *Reader
|
reader *Reader
|
||||||
index int
|
index int
|
||||||
files map[string]*editorFontXML
|
files map[string]*editorFontXML
|
||||||
fonts map[string]*editorFontDeclaration
|
fonts map[string]*editorFontDeclaration
|
||||||
unsafe bool
|
texts map[string][]editorFontText
|
||||||
|
fontCache editorBindingFontCache
|
||||||
|
unsafe bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorFontXML 保存字体声明及文字引用所在的XML
|
// editorFontXML 保存字体声明及文字引用所在的XML
|
||||||
type editorFontXML struct {
|
type editorFontXML struct {
|
||||||
name string
|
name string
|
||||||
data []byte
|
data []byte
|
||||||
root *editorXML
|
}
|
||||||
texts []*editorXML
|
|
||||||
|
// editorFontText 关联文字节点及其原始文件,按字体标识归集
|
||||||
|
type editorFontText struct {
|
||||||
|
file *editorFontXML
|
||||||
|
node *editorXML
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorFontDeclaration 关联字体声明、所属资源文件及嵌入数据路径
|
// editorFontDeclaration 关联字体声明、所属资源文件及嵌入数据路径
|
||||||
@@ -53,7 +59,7 @@ type editorFontDeclaration struct {
|
|||||||
// 入参: ctx 取消上下文, reader 包快照, index 文档索引
|
// 入参: ctx 取消上下文, reader 包快照, index 文档索引
|
||||||
// 返回: *editorFontDocument 字体引用, error 读取或结构错误
|
// 返回: *editorFontDocument 字体引用, error 读取或结构错误
|
||||||
func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFontDocument, error) {
|
func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFontDocument, error) {
|
||||||
scan := &editorFontDocument{reader: reader, index: index, files: make(map[string]*editorFontXML), fonts: make(map[string]*editorFontDeclaration)}
|
scan := &editorFontDocument{reader: reader, index: index, files: make(map[string]*editorFontXML), fonts: make(map[string]*editorFontDeclaration), texts: make(map[string][]editorFontText)}
|
||||||
entry, err := reader.documentEntryContext(ctx, index)
|
entry, err := reader.documentEntryContext(ctx, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -76,7 +82,7 @@ func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFon
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file := &editorFontXML{name: name, data: data, root: root}
|
file := &editorFontXML{name: name, data: data}
|
||||||
scan.files[name] = file
|
scan.files[name] = file
|
||||||
base := ""
|
base := ""
|
||||||
if root.matchesOFD("Res") {
|
if root.matchesOFD("Res") {
|
||||||
@@ -122,8 +128,9 @@ func scanEditorFonts(ctx context.Context, reader *Reader, index int) (*editorFon
|
|||||||
scan.fonts[id] = &editorFontDeclaration{font: font, file: file, node: node, location: location}
|
scan.fonts[id] = &editorFontDeclaration{font: font, file: file, node: node, location: location}
|
||||||
}
|
}
|
||||||
if node.matchesOFD("TextObject") || node.matchesOFD("Text") && node.parent != nil && node.parent.matchesOFD("Area") {
|
if node.matchesOFD("TextObject") || node.matchesOFD("Text") && node.parent != nil && node.parent.matchesOFD("Area") {
|
||||||
file.texts = append(file.texts, node)
|
id := editorResourceID(node.attr("Font"))
|
||||||
if node.attr("Font") == "" {
|
scan.texts[id] = append(scan.texts[id], editorFontText{file: file, node: node})
|
||||||
|
if id == "" {
|
||||||
scan.unsafe = true
|
scan.unsafe = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-21
@@ -17,6 +17,7 @@ package ofdgo
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"iter"
|
"iter"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -31,6 +32,32 @@ type graphicObjectTarget struct {
|
|||||||
composite *[]CompositeGraphicUnit
|
composite *[]CompositeGraphicUnit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// graphicAttributeTokens 提供图形节点的起止令牌,仅用于解码无命名空间属性
|
||||||
|
type graphicAttributeTokens struct {
|
||||||
|
start xml.StartElement
|
||||||
|
step int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token 返回起始节点和结束节点,不读取图形子节点
|
||||||
|
// 返回: xml.Token XML令牌, error 读取错误
|
||||||
|
func (s *graphicAttributeTokens) Token() (xml.Token, error) {
|
||||||
|
s.step++
|
||||||
|
switch s.step {
|
||||||
|
case 1:
|
||||||
|
return s.start, nil
|
||||||
|
case 2:
|
||||||
|
return s.start.End(), nil
|
||||||
|
}
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeGraphicAttributes 按结构字段解析图形属性,忽略带命名空间的扩展属性
|
||||||
|
// 入参: start 起始节点, value 不含自身解码方法的接收结构
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func decodeGraphicAttributes(start xml.StartElement, value any) error {
|
||||||
|
return xml.NewTokenDecoder(&graphicAttributeTokens{start: ofdXMLStart(start)}).Decode(value)
|
||||||
|
}
|
||||||
|
|
||||||
// Actions 返回对象动作的独立副本,不修改原对象
|
// Actions 返回对象动作的独立副本,不修改原对象
|
||||||
// 返回: []Action 动作列表
|
// 返回: []Action 动作列表
|
||||||
func (o GraphicObject) Actions() []Action {
|
func (o GraphicObject) Actions() []Action {
|
||||||
@@ -57,17 +84,45 @@ func (clips *Clips) usesObjectMatrix() bool {
|
|||||||
// 入参: d XML解码器, start 起始节点
|
// 入参: d XML解码器, start 起始节点
|
||||||
// 返回: error 解码错误
|
// 返回: error 解码错误
|
||||||
func (clips *Clips) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (clips *Clips) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
type plain Clips
|
*clips = Clips{}
|
||||||
var value plain
|
|
||||||
if classifyOFDNamespace(start.Name.Space) == ofdXMLLegacy {
|
if classifyOFDNamespace(start.Name.Space) == ofdXMLLegacy {
|
||||||
transform := true
|
transform := true
|
||||||
value.TransFlag = &transform
|
clips.TransFlag = &transform
|
||||||
}
|
}
|
||||||
if err := d.DecodeElement(&value, &start); err != nil {
|
for _, attr := range start.Attr {
|
||||||
return err
|
if attr.Name.Space == "" && attr.Name.Local == "TransFlag" {
|
||||||
|
transform, err := strconv.ParseBool(strings.TrimSpace(attr.Value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clips.TransFlag = &transform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*clips = Clips(value)
|
return decodeOFDCollection(d, start, "Clip", &clips.Clip)
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析裁剪分区,不将外部同名节点作为裁剪区域
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func (clip *Clip) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*clip = Clip{}
|
||||||
|
return decodeOFDCollection(d, start, "Area", &clip.Area)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析裁剪区域的变换和路径或文字内容
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func (area *ClipArea) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*area = ClipArea{DrawParam: attrValue(start, "DrawParam"), CTM: attrValue(start, "CTM")}
|
||||||
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
switch node.Name.Local {
|
||||||
|
case "Path":
|
||||||
|
return d.DecodeElement(&area.Path, &node)
|
||||||
|
case "Text":
|
||||||
|
return d.DecodeElement(&area.Text, &node)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML 解析路径并区分省略样式、显式实线与零值描边参数
|
// UnmarshalXML 解析路径并区分省略样式、显式实线与零值描边参数
|
||||||
@@ -75,27 +130,77 @@ func (clips *Clips) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (p *PathObject) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
func (p *PathObject) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
type plain PathObject
|
type plain PathObject
|
||||||
var value struct {
|
var value plain
|
||||||
plain
|
if err := decodeGraphicAttributes(start, &value); err != nil {
|
||||||
DashPattern *string `xml:"DashPattern,attr"`
|
|
||||||
}
|
|
||||||
if err := d.DecodeElement(&value, &start); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*p = PathObject(value.plain)
|
*p = PathObject(value)
|
||||||
for _, attr := range start.Attr {
|
for _, attr := range ofdXMLStart(start).Attr {
|
||||||
if attr.Name.Local == "LineWidth" {
|
switch attr.Name.Local {
|
||||||
|
case "LineWidth":
|
||||||
p.LineWidthSet = p.LineWidth == 0
|
p.LineWidthSet = p.LineWidth == 0
|
||||||
}
|
case "MiterLimit":
|
||||||
if attr.Name.Local == "MiterLimit" {
|
|
||||||
p.MiterLimitSet = p.MiterLimit == 0
|
p.MiterLimitSet = p.MiterLimit == 0
|
||||||
|
case "DashPattern":
|
||||||
|
p.dashPatternSet = p.DashPattern == ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value.DashPattern != nil {
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
p.DashPattern = *value.DashPattern
|
switch node.Name.Local {
|
||||||
p.dashPatternSet = p.DashPattern == ""
|
case "FillColor":
|
||||||
|
return d.DecodeElement(&p.FillColor, &node)
|
||||||
|
case "StrokeColor":
|
||||||
|
return d.DecodeElement(&p.StrokeColor, &node)
|
||||||
|
case "AbbreviatedData":
|
||||||
|
return d.DecodeElement(&p.AbbreviatedData, &node)
|
||||||
|
case "Clips":
|
||||||
|
return d.DecodeElement(&p.Clips, &node)
|
||||||
|
case "Actions":
|
||||||
|
return decodeOFDActions(d, node, &p.Actions)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析图片及其边框、裁剪和动作,忽略外部同名内容
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func (image *ImageObject) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type plain ImageObject
|
||||||
|
var value plain
|
||||||
|
if err := decodeGraphicAttributes(start, &value); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
*image = ImageObject(value)
|
||||||
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
switch node.Name.Local {
|
||||||
|
case "Border":
|
||||||
|
return d.DecodeElement(&image.Border, &node)
|
||||||
|
case "Clips":
|
||||||
|
return d.DecodeElement(&image.Clips, &node)
|
||||||
|
case "Actions":
|
||||||
|
return decodeOFDActions(d, node, &image.Actions)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析图片边框,保留省略线宽与显式零值的区别
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func (border *ImageBorder) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type plain ImageBorder
|
||||||
|
var value plain
|
||||||
|
if err := decodeGraphicAttributes(start, &value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*border = ImageBorder(value)
|
||||||
|
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
if node.Name.Local == "BorderColor" {
|
||||||
|
return d.DecodeElement(&border.BorderColor, &node)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML 解析绘制参数并保留显式实线和零值参数对基础参数的覆盖
|
// UnmarshalXML 解析绘制参数并保留显式实线和零值参数对基础参数的覆盖
|
||||||
|
|||||||
+74
-47
@@ -14,74 +14,101 @@
|
|||||||
|
|
||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
// textObjectTokens 记录字形变换所属TextCode,供解码后转换为对象内索引
|
"strconv"
|
||||||
type textObjectTokens struct {
|
"strings"
|
||||||
decoder *xml.Decoder
|
)
|
||||||
start *xml.StartElement
|
|
||||||
depth, code int
|
|
||||||
transforms []int
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalXML 解析字形变换,省略的字符数和字形数均取1
|
// UnmarshalXML 解析字形变换,省略的字符数和字形数均取1
|
||||||
// 入参: decoder XML解码器, start 变换起始节点
|
// 入参: decoder XML解码器, start 变换起始节点
|
||||||
// 返回: error 解码错误
|
// 返回: error 解码错误
|
||||||
func (transform *CGTransform) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
|
func (transform *CGTransform) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
|
||||||
type plain CGTransform
|
*transform = CGTransform{CodeCount: 1, GlyphCount: 1}
|
||||||
value := plain{CodeCount: 1, GlyphCount: 1}
|
for _, attr := range start.Attr {
|
||||||
|
if attr.Name.Space != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var target *int
|
||||||
|
switch attr.Name.Local {
|
||||||
|
case "CodePosition":
|
||||||
|
target = &transform.CodePosition
|
||||||
|
case "CodeCount":
|
||||||
|
target = &transform.CodeCount
|
||||||
|
case "GlyphCount":
|
||||||
|
target = &transform.GlyphCount
|
||||||
|
}
|
||||||
|
if target != nil {
|
||||||
|
value, err := strconv.Atoi(strings.TrimSpace(attr.Value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*target = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decodeOFDChildren(decoder, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
|
if node.Name.Local == "Glyphs" {
|
||||||
|
return d.DecodeElement(&transform.Glyphs, &node)
|
||||||
|
}
|
||||||
|
return d.Skip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析文字定位及原文,不使用带外部命名空间的定位属性
|
||||||
|
// 入参: decoder XML解码器, start 文字节点
|
||||||
|
// 返回: error 解码错误
|
||||||
|
func (code *TextCode) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type plain TextCode
|
||||||
|
var value plain
|
||||||
|
start = ofdXMLStart(start)
|
||||||
if err := decoder.DecodeElement(&value, &start); err != nil {
|
if err := decoder.DecodeElement(&value, &start); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*transform = CGTransform(value)
|
*code = TextCode(value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token 转发XML令牌并记录直接子节点顺序
|
// UnmarshalXML 解析标准文字节点,将各TextCode前的局部字形位置统一为对象内索引
|
||||||
// 返回: xml.Token XML令牌, error 解码错误
|
|
||||||
func (s *textObjectTokens) Token() (xml.Token, error) {
|
|
||||||
if s.start != nil {
|
|
||||||
start := *s.start
|
|
||||||
s.start = nil
|
|
||||||
return start, nil
|
|
||||||
}
|
|
||||||
token, err := s.decoder.Token()
|
|
||||||
switch value := token.(type) {
|
|
||||||
case xml.StartElement:
|
|
||||||
if s.depth == 0 && value.Name.Local == "CGTransform" {
|
|
||||||
s.transforms = append(s.transforms, s.code)
|
|
||||||
}
|
|
||||||
s.depth++
|
|
||||||
case xml.EndElement:
|
|
||||||
s.depth--
|
|
||||||
if s.depth == 0 && value.Name.Local == "TextCode" {
|
|
||||||
s.code++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalXML 将各TextCode前的局部字形位置统一为对象内索引
|
|
||||||
// 入参: decoder XML解码器, start 对象起始节点
|
// 入参: decoder XML解码器, start 对象起始节点
|
||||||
// 返回: error 解码错误
|
// 返回: error 解码错误
|
||||||
func (obj *TextObject) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
|
func (obj *TextObject) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
|
||||||
type plain TextObject
|
type plain TextObject
|
||||||
stream := &textObjectTokens{decoder: decoder, start: &start}
|
|
||||||
var value plain
|
var value plain
|
||||||
if err := xml.NewTokenDecoder(stream).Decode(&value); err != nil {
|
if err := decodeGraphicAttributes(start, &value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
offsets := make([]int, len(value.TextCode))
|
offset, pending := 0, 0
|
||||||
for i := 1; i < len(offsets); i++ {
|
if err := decodeOFDChildren(decoder, start, func(d *xml.Decoder, node xml.StartElement) error {
|
||||||
offsets[i] = offsets[i-1] + len(textCodeRunes(value.TextCode[i-1].Value))
|
switch node.Name.Local {
|
||||||
}
|
case "FillColor":
|
||||||
for i, code := range stream.transforms {
|
return d.DecodeElement(&value.FillColor, &node)
|
||||||
if code < len(offsets) {
|
case "StrokeColor":
|
||||||
value.CGTransform[i].CodePosition += offsets[code]
|
return d.DecodeElement(&value.StrokeColor, &node)
|
||||||
|
case "Clips":
|
||||||
|
return d.DecodeElement(&value.Clips, &node)
|
||||||
|
case "Actions":
|
||||||
|
return decodeOFDActions(d, node, &value.Actions)
|
||||||
|
case "CGTransform":
|
||||||
|
return d.DecodeElement(&value.CGTransform, &node)
|
||||||
|
case "TextCode":
|
||||||
|
var code TextCode
|
||||||
|
if err := d.DecodeElement(&code, &node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := pending; i < len(value.CGTransform); i++ {
|
||||||
|
value.CGTransform[i].CodePosition += offset
|
||||||
|
}
|
||||||
|
pending = len(value.CGTransform)
|
||||||
|
offset += len(textCodeRunes(code.Value))
|
||||||
|
value.TextCode = append(value.TextCode, code)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return d.Skip()
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
*obj = TextObject(value)
|
*obj = TextObject(value)
|
||||||
for _, attr := range start.Attr {
|
for _, attr := range ofdXMLStart(start).Attr {
|
||||||
if attr.Name.Local == "LineWidth" {
|
if attr.Name.Local == "LineWidth" {
|
||||||
obj.LineWidthSet = obj.LineWidth == 0
|
obj.LineWidthSet = obj.LineWidth == 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user