feat(命名空间): 规范声明顺序,修复命名空间
build.yaml / build (push) Successful in 49s

This commit is contained in:
xiaoqidun committed 2026-10-12 11:20:30 +08:00
1 parent 17a9a03361
commit c4b81155a9
19 files changed
+410 -142

No files matched your search

+8 -8
View File
@@ -126,6 +126,14 @@ type annotationOptions struct {
Area *ofdgo.Box Area *ofdgo.Box
} }
// outputContext 定期让出浏览器线程,将取消请求传入库层长操作
type outputContext struct {
context.Context
cancel context.CancelFunc
checkpoint func() error
checked time.Time
}
// Write 将数据块交给浏览器保存 // Write 将数据块交给浏览器保存
// 入参: data 导出数据 // 入参: data 导出数据
// 返回: int 写入长度, error 错误信息 // 返回: int 写入长度, error 错误信息
@@ -4626,14 +4634,6 @@ func compressionFromJS(value js.Value, options *ofdgo.CompressionOptions) error
return options.Validate() return options.Validate()
} }
// outputContext 定期让出浏览器线程,将取消请求传入库层长操作
type outputContext struct {
context.Context
cancel context.CancelFunc
checkpoint func() error
checked time.Time
}
// newOutputContext 创建单次串行导出的取消上下文 // newOutputContext 创建单次串行导出的取消上下文
// 入参: checkpoint 浏览器取消检查回调 // 入参: checkpoint 浏览器取消检查回调
// 返回: context.Context 导出上下文 // 返回: context.Context 导出上下文
+17
View File
@@ -139,6 +139,23 @@ type actionSource struct {
Graphic bool Graphic bool
} }
// decodeOFDActions 读取标准动作列表,不将外部同名节点作为交互动作
// 入参: d XML解码器, start 列表起始节点, actions 动作接收列表
// 返回: error 解析错误
func decodeOFDActions(d *xml.Decoder, start xml.StartElement, actions *[]Action) error {
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local != "Action" {
return d.Skip()
}
var action Action
if err := d.DecodeElement(&action, &node); err != nil {
return err
}
*actions = append(*actions, action)
return nil
})
}
// UnmarshalXML 解析跳转目标 // UnmarshalXML 解析跳转目标
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
+107 -13
View File
@@ -16,6 +16,7 @@ package ofdgo
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"path" "path"
"strings" "strings"
) )
@@ -202,6 +203,112 @@ type print struct {
Copies *int `xml:"Copies,attr"` Copies *int `xml:"Copies,attr"`
} }
// UnmarshalXML 按命名空间读取文档结构,扩展数据仍由原解码器保留原文
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if start.Name.Local != "Document" {
return fmt.Errorf("expected Document root, got %s", start.Name.Local)
}
*doc = Document{XMLName: start.Name, Permissions: defaultPermissions()}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
var target any
switch node.Name.Local {
case "CommonData":
target = &doc.CommonData
case "Pages":
target = &doc.Pages
case "Outlines":
target = &doc.Outlines
case "Permissions":
target = &doc.Permissions
case "Actions":
return decodeOFDActions(d, node, &doc.Actions)
case "Bookmarks":
target = &doc.Bookmarks
case "Annotations":
target = &doc.Annotations
case "Attachments":
target = &doc.Attachments
case "CustomTags":
target = &doc.CustomTags
case "Extensions":
target = &doc.Extensions
default:
return d.Skip()
}
return d.DecodeElement(target, &node)
})
}
// UnmarshalXML 读取文档公共区域、模板与资源引用,忽略外部同名节点
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (common *CommonData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*common = CommonData{}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
var target any
switch node.Name.Local {
case "MaxUnitID":
target = &common.MaxUnitID
case "PageArea":
target = &common.PageArea
case "PublicRes":
target = &common.PublicRes
case "DocumentRes":
target = &common.DocumentRes
case "TemplatePage":
target = &common.TemplatePage
case "DefaultCS":
target = &common.DefaultCS
default:
return d.Skip()
}
return d.DecodeElement(target, &node)
})
}
// UnmarshalXML 读取页面区域,防止外部同名字段覆盖标准区域
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (area *PageArea) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*area = PageArea{}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
var target *string
switch node.Name.Local {
case "PhysicalBox":
target = &area.PhysicalBox
case "ApplicationBox":
target = &area.ApplicationBox
case "ContentBox":
target = &area.ContentBox
case "BleedBox":
target = &area.BleedBox
default:
return d.Skip()
}
return d.DecodeElement(target, &node)
})
}
// UnmarshalXML 读取标准页面引用,忽略外部扩展中的同名条目
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (pages *Pages) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*pages = Pages{}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local != "Page" {
return d.Skip()
}
var page Page
if err := d.DecodeElement(&page, &node); err != nil {
return err
}
pages.Page = append(pages.Page, page)
return nil
})
}
// PageIndex 按标识查找页面,优先匹配原文和标准十进制写法,等价编号有歧义时不匹配 // PageIndex 按标识查找页面,优先匹配原文和标准十进制写法,等价编号有歧义时不匹配
// 入参: id 页面标识 // 入参: id 页面标识
// 返回: int 从0开始的页面索引,未找到时为-1 // 返回: int 从0开始的页面索引,未找到时为-1
@@ -249,19 +356,6 @@ func (doc *Document) OutlineInfos() []OutlineInfo {
return outlineInfos(doc.Outlines.OutlineElem, bookmarks, pages) return outlineInfos(doc.Outlines.OutlineElem, bookmarks, pages)
} }
// UnmarshalXML 解析文档并应用权限默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type document Document
value := document{Permissions: defaultPermissions()}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*doc = Document(value)
return nil
}
// FileName 获取另存文件名,优先使用带扩展名的显示名称,剔除目录及文件名非法字符 // FileName 获取另存文件名,优先使用带扩展名的显示名称,剔除目录及文件名非法字符
// 返回: string 文件名 // 返回: string 文件名
func (a Attachment) FileName() string { func (a Attachment) FileName() string {
+3 -3
View File
@@ -28,9 +28,6 @@ import (
"strings" "strings"
) )
// FontMode 指定字体资源的保存方式,不改变文字位置或压缩选项
type FontMode uint8
const ( const (
// FontKeep 保留原有嵌入方式 // FontKeep 保留原有嵌入方式
FontKeep FontMode = iota FontKeep FontMode = iota
@@ -40,6 +37,9 @@ const (
FontExternal FontExternal
) )
// FontMode 指定字体资源的保存方式,不改变文字位置或压缩选项
type FontMode uint8
// FontChange 返回单个字体的处理结果,Document为零基文档索引,Reason非空表示保持原样 // FontChange 返回单个字体的处理结果,Document为零基文档索引,Reason非空表示保持原样
type FontChange struct { type FontChange struct {
Document int `json:"document"` Document int `json:"document"`
+7 -1
View File
@@ -24,7 +24,13 @@ import (
// 入参: name 节点名称 // 入参: name 节点名称
// 返回: bool 是否匹配 // 返回: bool 是否匹配
func (n *editorXML) matchesOFD(name string) bool { func (n *editorXML) matchesOFD(name string) bool {
return n.name.Local == name && (classifyOFDNamespace(n.name.Space) != ofdXMLUnknown || n.parent != nil && n.name.Space == n.parent.name.Space) if n.name.Local != name {
return false
}
if n.parent == nil {
return classifyOFDNamespace(n.name.Space) != ofdXMLUnknown
}
return ofdXMLChild(n.parent.name, n.name)
} }
// textXML 按所在节点的命名空间编码文本节点 // textXML 按所在节点的命名空间编码文本节点
+5 -2
View File
@@ -380,6 +380,9 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo
var visit func(*editorXML) error var visit func(*editorXML) error
visit = func(parent *editorXML) error { visit = func(parent *editorXML) error {
for _, node := range parent.children { for _, node := range parent.children {
if !ofdXMLChild(parent.name, node.name) {
continue
}
id := node.attr("ID") id := node.attr("ID")
if id != "" && seen[id] { if id != "" && seen[id] {
switch node.name.Local { switch node.name.Local {
@@ -401,7 +404,7 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo
} }
if content := root.child("Content"); content != nil { if content := root.child("Content"); content != nil {
for _, layer := range content.children { for _, layer := range content.children {
if layer.name.Local == "Layer" { if layer.matchesOFD("Layer") {
if err := visit(layer); err != nil { if err := visit(layer); err != nil {
return nil, false, err return nil, false, err
} }
@@ -508,7 +511,7 @@ func (e *Editor) loadSourcePage(index int) error {
nodes := make(map[string]*editorXML) nodes := make(map[string]*editorXML)
if content := root.child("Content"); content != nil { if content := root.child("Content"); content != nil {
for _, layer := range content.children { for _, layer := range content.children {
if layer.name.Local != "Layer" { if !layer.matchesOFD("Layer") {
continue continue
} }
if err := editorXMLObjects(layer, nodes); err != nil { if err := editorXMLObjects(layer, nodes); err != nil {
+8 -8
View File
@@ -24,6 +24,14 @@ import (
"strings" "strings"
) )
// pageReferenceNode 记录跳转引用扫描中的父节点及删除范围
type pageReferenceNode struct {
name string
start, patch int
children, removed int
standard, drop bool
}
// pruneDocumentReferences 在输出快照中清理已删除页面和附件的引用,不修改历史快照或其他文档 // pruneDocumentReferences 在输出快照中清理已删除页面和附件的引用,不修改历史快照或其他文档
// 沿当前文档、页面、模板、资源、注释和签名索引检查,不进入附件、私有扩展和签名原始凭据 // 沿当前文档、页面、模板、资源、注释和签名索引检查,不进入附件、私有扩展和签名原始凭据
// 版本包保留原签名及其页面引用,避免改写其他版本共用的签名数据 // 版本包保留原签名及其页面引用,避免改写其他版本共用的签名数据
@@ -185,14 +193,6 @@ func (e *Editor) pruneDocumentReferences(parts map[string][]byte, generated map[
return ctx.Err() return ctx.Err()
} }
// pageReferenceNode 记录跳转引用扫描中的父节点及删除范围
type pageReferenceNode struct {
name string
start, patch int
children, removed int
standard, drop bool
}
// pruneCreatedPageReferences 清理创作文档中被明确删除页面的直接跳转,不检查其他目标是否可达 // pruneCreatedPageReferences 清理创作文档中被明确删除页面的直接跳转,不检查其他目标是否可达
// 入参: ctx 取消上下文, data 创作XML, removed 已删除页面标识 // 入参: ctx 取消上下文, data 创作XML, removed 已删除页面标识
// 返回: []byte 更新后的XML, error 解析或取消错误 // 返回: []byte 更新后的XML, error 解析或取消错误
+22 -22
View File
@@ -33,6 +33,28 @@ type editorGeneratedReferences struct {
staged *editorStagedPage staged *editorStagedPage
} }
// editorVectorUsage 保存自产矢量资源的引用快照,内容和路径变化时失效
type editorVectorUsage struct {
name string
digest [32]byte
refs *editorResourceRefs
}
// editorReferenceScan 按XML事件收集资源和字体用字,未知内容保持完整扫描
type editorReferenceScan struct {
refs *editorResourceRefs
objects map[string]bool
name string
base string
stack []string
fonts []*editorFontUsage
text strings.Builder
resource bool
capture bool
safe bool
seen bool
}
// matches 判断条目仍为本次编码结果,空条目与暂存占位不混用 // matches 判断条目仍为本次编码结果,空条目与暂存占位不混用
// 入参: data 当前条目数据 // 入参: data 当前条目数据
// 返回: bool 是否可以复用本次引用 // 返回: bool 是否可以复用本次引用
@@ -50,13 +72,6 @@ func (r editorGeneratedReferences) stagedLeaf(data []byte) bool {
return r.staged != nil && data == nil && r.refs != nil && len(r.refs.files) == 0 return r.staged != nil && data == nil && r.refs != nil && len(r.refs.files) == 0
} }
// editorVectorUsage 保存自产矢量资源的引用快照,内容和路径变化时失效
type editorVectorUsage struct {
name string
digest [32]byte
refs *editorResourceRefs
}
// matches 核对资源路径和完整内容摘要,不以切片地址判断内容 // matches 核对资源路径和完整内容摘要,不以切片地址判断内容
// 入参: name 当前资源路径, data 当前资源内容 // 入参: name 当前资源路径, data 当前资源内容
// 返回: bool 是否仍可使用编码时的引用 // 返回: bool 是否仍可使用编码时的引用
@@ -89,21 +104,6 @@ func (e *Editor) generatedVectorReferences(parts map[string][]byte, generated ma
return generated return generated
} }
// editorReferenceScan 按XML事件收集资源和字体用字,未知内容保持完整扫描
type editorReferenceScan struct {
refs *editorResourceRefs
objects map[string]bool
name string
base string
stack []string
fonts []*editorFontUsage
text strings.Builder
resource bool
capture bool
safe bool
seen bool
}
// vectorUsage 保留完整且限额内的自产矢量引用,超限时保存流程重新扫描 // vectorUsage 保留完整且限额内的自产矢量引用,超限时保存流程重新扫描
// 入参: data 本次编码内容 // 入参: data 本次编码内容
// 返回: *editorVectorUsage 引用快照,无法复用时为空 // 返回: *editorVectorUsage 引用快照,无法复用时为空
+4 -1
View File
@@ -273,11 +273,14 @@ func editorXMLTextValue(data []byte, node *editorXML, before, after string) ([]b
return append(entry, data[patch.end:node.end]...), nil return append(entry, data[patch.end:node.end]...), nil
} }
// editorXMLObjects 递归登记图层和页块内的对象位置,保留原容器层级 // editorXMLObjects 递归登记图层和页块内的OFD对象位置,不进入外部扩展
// 入参: container 图层或页块, nodes 标识与节点映射 // 入参: container 图层或页块, nodes 标识与节点映射
// 返回: error 错误信息 // 返回: error 错误信息
func editorXMLObjects(container *editorXML, nodes map[string]*editorXML) error { func editorXMLObjects(container *editorXML, nodes map[string]*editorXML) error {
for _, node := range container.children { for _, node := range container.children {
if !ofdXMLChild(container.name, node.name) {
continue
}
if id := node.attr("ID"); id != "" { if id := node.attr("ID"); id != "" {
if nodes[id] != nil { if nodes[id] != nil {
return fmt.Errorf("duplicate object ID %q", id) return fmt.Errorf("duplicate object ID %q", id)
+3 -3
View File
@@ -220,11 +220,11 @@ func (p *fontUsagePage) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
if start.Name.Local != "Page" { if start.Name.Local != "Page" {
return fmt.Errorf("expected element type <Page> but have <%s>", start.Name.Local) return fmt.Errorf("expected element type <Page> but have <%s>", start.Name.Local)
} }
return decodeObjectContainer(d, start, func(d *xml.Decoder, node xml.StartElement) error { return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local != "Content" { if node.Name.Local != "Content" {
return d.Skip() return d.Skip()
} }
return decodeObjectContainer(d, node, func(d *xml.Decoder, layer xml.StartElement) error { return decodeOFDChildren(d, node, func(d *xml.Decoder, layer xml.StartElement) error {
if layer.Name.Local != "Layer" { if layer.Name.Local != "Layer" {
return d.Skip() return d.Skip()
} }
@@ -434,7 +434,7 @@ func (r *Renderer) readPageFontUsage(page Page) (map[string]int, error) {
// 入参: d XML解码器, start 容器起始节点, defaults 默认绘制参数 // 入参: d XML解码器, start 容器起始节点, defaults 默认绘制参数
// 返回: error 错误信息 // 返回: error 错误信息
func (p *fontUsagePage) decodeObjects(d *xml.Decoder, start xml.StartElement, defaults *DrawParam) error { func (p *fontUsagePage) decodeObjects(d *xml.Decoder, start xml.StartElement, defaults *DrawParam) error {
return decodeObjectContainer(d, start, func(d *xml.Decoder, node xml.StartElement) error { return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
switch node.Name.Local { switch node.Name.Local {
case "PageBlock": case "PageBlock":
return p.decodeObjects(d, node, defaults) return p.decodeObjects(d, node, defaults)
+7 -7
View File
@@ -23,6 +23,13 @@ import (
"math/bits" "math/bits"
) )
// bmpColorMask 保存一个BMP颜色分量的位域位置和取值范围
type bmpColorMask struct {
mask uint32
shift uint
max uint32
}
// decodeImageConfig 读取图片尺寸,BMP和TIFF只读取布局信息,不解码像素 // decodeImageConfig 读取图片尺寸,BMP和TIFF只读取布局信息,不解码像素
// 入参: input 图片输入流 // 入参: input 图片输入流
// 返回: image.Config 图片配置, string 图片格式, error 头部错误 // 返回: image.Config 图片配置, string 图片格式, error 头部错误
@@ -57,13 +64,6 @@ func decodeImageConfig(input io.Reader) (image.Config, string, error) {
return image.DecodeConfig(buffer) return image.DecodeConfig(buffer)
} }
// bmpColorMask 保存一个BMP颜色分量的位域位置和取值范围
type bmpColorMask struct {
mask uint32
shift uint
max uint32
}
// sample 将位域采样值映射到八位通道,缺省透明度为不透明 // sample 将位域采样值映射到八位通道,缺省透明度为不透明
// 入参: value 编码像素 // 入参: value 编码像素
// 返回: uint8 通道值 // 返回: uint8 通道值
+3 -3
View File
@@ -25,6 +25,9 @@ import (
"io" "io"
) )
// errTIFFRandomAccess 表示目录链需要回读,仅流式输入需重新打开资源
var errTIFFRandomAccess = errors.New("TIFF directory requires random access")
// tiffImageLayout 保存单页TIFF的尺寸、方向、采样及压缩参数 // tiffImageLayout 保存单页TIFF的尺寸、方向、采样及压缩参数
type tiffImageLayout struct { type tiffImageLayout struct {
width, height int width, height int
@@ -39,9 +42,6 @@ type tiffImageLayout struct {
t4, t6 uint32 t4, t6 uint32
} }
// errTIFFRandomAccess 表示目录链需要回读,仅流式输入需重新打开资源
var errTIFFRandomAccess = errors.New("TIFF directory requires random access")
// tiffDirectoryInput 只替换解码视图中的首目录偏移,不复制或改写原始资源 // tiffDirectoryInput 只替换解码视图中的首目录偏移,不复制或改写原始资源
type tiffDirectoryInput struct { type tiffDirectoryInput struct {
reader *bytes.Reader reader *bytes.Reader
+45 -18
View File
@@ -14,7 +14,10 @@
package ofdgo package ofdgo
import "encoding/xml" import (
"encoding/xml"
"fmt"
)
// ofdCreator 标识本库生成的文档 // ofdCreator 标识本库生成的文档
const ofdCreator = "xiaoqidun/ofdgo" const ofdCreator = "xiaoqidun/ofdgo"
@@ -37,23 +40,6 @@ type DocBody struct {
versioned bool versioned bool
} }
// UnmarshalXML 读取文档入口并记录版本列表是否存在,版本描述在选择时读取
// 入参: decoder XML解码器, start 起始节点
// 返回: error 解析错误
func (body *DocBody) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
type docBody DocBody
var value struct {
docBody
Versions *struct{} `xml:"Versions"`
}
if err := decoder.DecodeElement(&value, &start); err != nil {
return err
}
*body = DocBody(value.docBody)
body.versioned = value.Versions != nil
return nil
}
// DocInfo 文档元数据 // DocInfo 文档元数据
type DocInfo struct { type DocInfo struct {
DocID string `xml:"DocID"` DocID string `xml:"DocID"`
@@ -79,3 +65,44 @@ type CustomData struct {
Value string `xml:",chardata"` Value string `xml:",chardata"`
sourceIndex int sourceIndex int
} }
// UnmarshalXML 读取主入口,区分标准文档条目与扩展中的同名节点
// 入参: decoder XML解码器, start 起始节点
// 返回: error 解析错误
func (ofd *OFD) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
if start.Name.Local != "OFD" {
return fmt.Errorf("expected OFD root, got %s", start.Name.Local)
}
*ofd = OFD{XMLName: start.Name, Version: attrValue(start, "Version"), DocType: attrValue(start, "DocType")}
return decodeOFDChildren(decoder, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local != "DocBody" {
return d.Skip()
}
var body DocBody
if err := d.DecodeElement(&body, &node); err != nil {
return err
}
ofd.DocBody = append(ofd.DocBody, body)
return nil
})
}
// UnmarshalXML 读取文档入口并记录版本列表是否存在,版本描述在选择时读取
// 入参: decoder XML解码器, start 起始节点
// 返回: error 解析错误
func (body *DocBody) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error {
*body = DocBody{}
return decodeOFDChildren(decoder, start, func(d *xml.Decoder, node xml.StartElement) error {
switch node.Name.Local {
case "DocInfo":
return d.DecodeElement(&body.DocInfo, &node)
case "DocRoot":
return d.DecodeElement(&body.DocRoot, &node)
case "Signatures":
return d.DecodeElement(&body.Signatures, &node)
case "Versions":
body.versioned = true
}
return d.Skip()
})
}
+11 -39
View File
@@ -162,7 +162,7 @@ func (l *Layer) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
l.ID = attrValue(start, "ID") l.ID = attrValue(start, "ID")
l.Type = attrValue(start, "Type") l.Type = attrValue(start, "Type")
l.DrawParam = attrValue(start, "DrawParam") l.DrawParam = attrValue(start, "DrawParam")
return decodeObjectContainer(d, start, l.decodeObject) return decodeOFDChildren(d, start, l.decodeObject)
} }
// UnmarshalXML 解析复合图元并保留对象顺序 // UnmarshalXML 解析复合图元并保留对象顺序
@@ -199,7 +199,7 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme
c.Visible = &visible c.Visible = &visible
} }
} }
return decodeObjectContainer(d, start, c.decodeObject) return decodeOFDChildren(d, start, c.decodeObject)
} }
// UnmarshalXML 解析注释外观并保留对象顺序 // UnmarshalXML 解析注释外观并保留对象顺序
@@ -208,7 +208,7 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme
func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*a = Appearance{} *a = Appearance{}
a.Boundary = attrValue(start, "Boundary") a.Boundary = attrValue(start, "Boundary")
return decodeObjectContainer(d, start, a.decodeObject) return decodeOFDChildren(d, start, a.decodeObject)
} }
// UnmarshalXML 解析图案单元内容并保留对象顺序 // UnmarshalXML 解析图案单元内容并保留对象顺序
@@ -216,7 +216,7 @@ func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
// 返回: error 错误信息 // 返回: error 错误信息
func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*p = PatternContent{Thumbnail: attrValue(start, "Thumbnail")} *p = PatternContent{Thumbnail: attrValue(start, "Thumbnail")}
return decodeObjectContainer(d, start, p.decodeObject) return decodeOFDChildren(d, start, p.decodeObject)
} }
// objects 按绘制顺序访问底纹单元,Objects优先于分类数组 // objects 按绘制顺序访问底纹单元,Objects优先于分类数组
@@ -314,28 +314,6 @@ func decodeGraphicObject(d *xml.Decoder, start xml.StartElement, target graphicO
return true, nil return true, nil
} }
// decodeObjectContainer 解析图形对象容器
// 入参: d XML解码器, start 起始节点, decode 对象解码函数
// 返回: error 错误信息
func decodeObjectContainer(d *xml.Decoder, start xml.StartElement, decode func(*xml.Decoder, xml.StartElement) error) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := decode(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// decodeObject 解析图层子对象 // decodeObject 解析图层子对象
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
@@ -351,7 +329,7 @@ func (l *Layer) decodeObject(d *xml.Decoder, start xml.StartElement) error {
return err return err
} }
if start.Name.Local == "PageBlock" { if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, l.decodeObject) return decodeOFDChildren(d, start, l.decodeObject)
} }
return d.Skip() return d.Skip()
} }
@@ -388,15 +366,9 @@ func (c *CompositeGraphicUnit) decodeObject(d *xml.Decoder, start xml.StartEleme
} }
c.Clips = &clips c.Clips = &clips
case "Actions": case "Actions":
var actions struct { return decodeOFDActions(d, start, &c.Actions)
Action []Action `xml:"Action"`
}
if err := d.DecodeElement(&actions, &start); err != nil {
return err
}
c.Actions = actions.Action
case "Content", "PageBlock": case "Content", "PageBlock":
return decodeObjectContainer(d, start, c.decodeObject) return decodeOFDChildren(d, start, c.decodeObject)
default: default:
return d.Skip() return d.Skip()
} }
@@ -418,7 +390,7 @@ func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error
return err return err
} }
if start.Name.Local == "PageBlock" { if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, a.decodeObject) return decodeOFDChildren(d, start, a.decodeObject)
} }
return d.Skip() return d.Skip()
} }
@@ -438,17 +410,17 @@ func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) er
return err return err
} }
if start.Name.Local == "PageBlock" { if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, p.decodeObject) return decodeOFDChildren(d, start, p.decodeObject)
} }
return d.Skip() return d.Skip()
} }
// attrValue 获取XML属性值 // attrValue 获取无命名空间的XML属性值
// 入参: start 起始节点, name 属性名 // 入参: start 起始节点, name 属性名
// 返回: string 属性值 // 返回: string 属性值
func attrValue(start xml.StartElement, name string) string { func attrValue(start xml.StartElement, name string) string {
for _, attr := range start.Attr { for _, attr := range start.Attr {
if attr.Name.Local == name { if attr.Name.Space == "" && attr.Name.Local == name {
return attr.Value return attr.Value
} }
} }
+57 -1
View File
@@ -14,7 +14,10 @@
package ofdgo package ofdgo
import "encoding/xml" import (
"encoding/xml"
"fmt"
)
// PageContent 页面内容 // PageContent 页面内容
type PageContent struct { type PageContent struct {
@@ -270,3 +273,56 @@ type ImageBorder struct {
DashPattern string `xml:"DashPattern,attr"` DashPattern string `xml:"DashPattern,attr"`
BorderColor *StrokeColor `xml:"BorderColor"` BorderColor *StrokeColor `xml:"BorderColor"`
} }
// UnmarshalXML 读取页面直接子节点,扩展内容不作为页面区域、资源或图层使用
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (page *PageContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if start.Name.Local != "Page" {
return fmt.Errorf("expected Page root, got %s", start.Name.Local)
}
*page = PageContent{XMLName: start.Name}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
switch node.Name.Local {
case "Area":
return d.DecodeElement(&page.Area, &node)
case "Template":
var template Template
if err := d.DecodeElement(&template, &node); err != nil {
return err
}
page.Template = append(page.Template, template)
case "PageRes":
var location string
if err := d.DecodeElement(&location, &node); err != nil {
return err
}
page.PageRes = append(page.PageRes, location)
case "Content":
return d.DecodeElement(&page.Content, &node)
case "Actions":
return decodeOFDActions(d, node, &page.Actions)
default:
return d.Skip()
}
return nil
})
}
// UnmarshalXML 读取页面图层,不将外部命名空间中的Layer作为图层
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (content *Content) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*content = Content{}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local != "Layer" {
return d.Skip()
}
var layer Layer
if err := d.DecodeElement(&layer, &node); err != nil {
return err
}
content.Layer = append(content.Layer, layer)
return nil
})
}
+37 -6
View File
@@ -62,6 +62,27 @@ type Reader struct {
encryption *encryptionState encryption *encryptionState
} }
// documentPageList 仅读取文档中的页面索引,统计页数时不构建其他结构
type documentPageList struct {
Pages Pages
}
// UnmarshalXML 按文档命名空间读取页面列表,跳过其他文档结构和扩展
// 入参: d XML解码器, start 起始节点
// 返回: error 解析错误
func (list *documentPageList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if start.Name.Local != "Document" {
return fmt.Errorf("expected Document root, got %s", start.Name.Local)
}
*list = documentPageList{}
return decodeOFDChildren(d, start, func(d *xml.Decoder, node xml.StartElement) error {
if node.Name.Local == "Pages" {
return d.DecodeElement(&list.Pages, &node)
}
return d.Skip()
})
}
// Close 关闭阅读器持有的文件,重复调用不再关闭底层文件 // Close 关闭阅读器持有的文件,重复调用不再关闭底层文件
// 返回: error 错误信息 // 返回: error 错误信息
func (r *Reader) Close() error { func (r *Reader) Close() error {
@@ -117,10 +138,7 @@ func (r *Reader) DocumentPageCountContext(ctx context.Context, index int) (int,
if err != nil { if err != nil {
return 0, err return 0, err
} }
var doc struct { var doc documentPageList
XMLName xml.Name `xml:"Document"`
Pages Pages `xml:"Pages"`
}
decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
if err := decoder.Decode(&doc); err != nil { if err := decoder.Decode(&doc); err != nil {
return 0, fmt.Errorf("failed to unmarshal document.xml: %w", err) return 0, fmt.Errorf("failed to unmarshal document.xml: %w", err)
@@ -1003,6 +1021,7 @@ func (r *Reader) readPageHeaderContext(ctx context.Context, page Page) (PageCont
defer f.Close() defer f.Close()
var content PageContent var content PageContent
d := xml.NewDecoder(imageInput{ReadCloser: f, context: ctx}) d := xml.NewDecoder(imageInput{ReadCloser: f, context: ctx})
var root xml.Name
scan: scan:
for { for {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
@@ -1016,9 +1035,21 @@ scan:
return PageContent{}, err return PageContent{}, err
} }
if start, ok := token.(xml.StartElement); ok { if start, ok := token.(xml.StartElement); ok {
switch start.Name.Local { if root.Local == "" {
case "Page": if start.Name.Local != "Page" {
return PageContent{}, fmt.Errorf("expected Page root, got %s", start.Name.Local)
}
root = start.Name
continue continue
}
if !ofdXMLChild(root, start.Name) {
if err := d.Skip(); err != nil {
return PageContent{}, err
}
continue
}
start = ofdXMLStart(start)
switch start.Name.Local {
case "Template": case "Template":
var template Template var template Template
if err := d.DecodeElement(&template, &start); err != nil { if err := d.DecodeElement(&template, &start); err != nil {
+3 -3
View File
@@ -61,6 +61,9 @@ type Renderer struct {
TransparentBackground bool TransparentBackground bool
} }
// RendererOption 渲染器配置选项
type RendererOption func(*Renderer)
// deviceLineWidth 按设备分辨率计算描边宽度,零线宽为一像素,正线宽至少为两像素 // deviceLineWidth 按设备分辨率计算描边宽度,零线宽为一像素,正线宽至少为两像素
// 入参: width 页面空间线宽,单位为毫米 // 入参: width 页面空间线宽,单位为毫米
// 返回: float64 设备描边宽度,单位为毫米 // 返回: float64 设备描边宽度,单位为毫米
@@ -75,9 +78,6 @@ func (r *Renderer) deviceLineWidth(width float64) float64 {
return width return width
} }
// RendererOption 渲染器配置选项
type RendererOption func(*Renderer)
// ClearCache 释放派生字体、图片、模板与后端缓存,不改变文档和渲染配置 // ClearCache 释放派生字体、图片、模板与后端缓存,不改变文档和渲染配置
func (r *Renderer) ClearCache() { func (r *Renderer) ClearCache() {
r.backendStates = make(map[any]any) r.backendStates = make(map[any]any)
+3 -3
View File
@@ -19,9 +19,6 @@ import (
"math" "math"
) )
// U3DRenderMode 定义静态几何的呈现方式,边线和顶点使用单像素覆盖
type U3DRenderMode uint8
const ( const (
U3DRenderSolid U3DRenderMode = iota // 实体 U3DRenderSolid U3DRenderMode = iota // 实体
U3DRenderSolidWireframe // 实体线框 U3DRenderSolidWireframe // 实体线框
@@ -38,6 +35,9 @@ const (
U3DRenderShadedIllustration // 着色插图 U3DRenderShadedIllustration // 着色插图
) )
// U3DRenderMode 定义静态几何的呈现方式,边线和顶点使用单像素覆盖
type U3DRenderMode uint8
// u3dPrimitiveKey 区分共享点线的颜色及混合状态,避免透明边点重复叠加 // u3dPrimitiveKey 区分共享点线的颜色及混合状态,避免透明边点重复叠加
type u3dPrimitiveKey struct { type u3dPrimitiveKey struct {
a, b [7]float64 a, b [7]float64
+60 -1
View File
@@ -14,7 +14,10 @@
package ofdgo package ofdgo
import "unicode/utf8" import (
"encoding/xml"
"unicode/utf8"
)
// 旧版与2016版OFD命名空间 // 旧版与2016版OFD命名空间
const ( const (
@@ -33,6 +36,62 @@ const (
// ofdXMLNamespace 标识XML命名空间,不代表文档版本或功能支持范围 // ofdXMLNamespace 标识XML命名空间,不代表文档版本或功能支持范围
type ofdXMLNamespace uint8 type ofdXMLNamespace uint8
// ofdXMLChild 判断子节点是否属于可识别的OFD命名空间或父节点所在的未知命名空间
// 入参: parent 父节点名称, child 子节点名称
// 返回: bool 是否属于同一OFD结构
func ofdXMLChild(parent, child xml.Name) bool {
return classifyOFDNamespace(child.Space) != ofdXMLUnknown || child.Space == parent.Space
}
// ofdXMLStart 保留OFD节点的无命名空间属性,不修改解码器返回的属性切片
// 入参: start 起始节点
// 返回: xml.StartElement 供标准结构解码使用的节点
func ofdXMLStart(start xml.StartElement) xml.StartElement {
for i, attr := range start.Attr {
if attr.Name.Space == "" {
continue
}
attrs := make([]xml.Attr, i, len(start.Attr)-1)
copy(attrs, start.Attr[:i])
for _, attr := range start.Attr[i+1:] {
if attr.Name.Space == "" {
attrs = append(attrs, attr)
}
}
start.Attr = attrs
break
}
return start
}
// decodeOFDChildren 按命名空间解析直接子节点,跳过外部扩展而不提升其内部节点
// 入参: d XML解码器, start 容器起始节点, decode 子节点解码函数
// 返回: error 解析错误
func decodeOFDChildren(d *xml.Decoder, start xml.StartElement, decode func(*xml.Decoder, xml.StartElement) error) error {
for {
token, err := d.Token()
if err != nil {
return err
}
switch node := token.(type) {
case xml.StartElement:
if !ofdXMLChild(start.Name, node.Name) {
if err := d.Skip(); err != nil {
return err
}
continue
}
if err := decode(d, ofdXMLStart(node)); err != nil {
return err
}
case xml.EndElement:
if node.Name == start.Name {
return nil
}
}
}
}
// classifyOFDNamespace 识别OFD命名空间,未声明与未知版本分别处理 // classifyOFDNamespace 识别OFD命名空间,未声明与未知版本分别处理
// 入参: namespace XML命名空间 // 入参: namespace XML命名空间
// 返回: ofdXMLNamespace 命名空间类别 // 返回: ofdXMLNamespace 命名空间类别