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