mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
17a9a03361
commit
c4b81155a9
19 files changed
+410
-142
No files matched your search
@@ -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 导出上下文
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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 按所在节点的命名空间编码文本节点
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 命名空间类别
|
||||
|
||||
Reference in new issue
Block a user