feat(字体处理): 优化字体与文案,修复文档隔离
build.yaml / build (push) Successful in 3m22s

This commit is contained in:
xiaoqidun committed 2026-10-10 12:24:32 +08:00
1 parent c2d9a349c5
commit 0f2fb7a842
17 files changed
+105 -53

No files matched your search

+2 -2
View File
@@ -57,7 +57,7 @@
<option value="300" selected>300 dpi</option>
<option value="600">600 dpi</option>
</select><span id="dpiValue" aria-hidden="true">300</span></label>
<label class="toolbar-select"><select id="outputCompression" class="format-select" title="压缩模式" aria-label="压缩模式" disabled><option value="0">默认</option><option value="1">无损</option><option value="2">轻压</option><option value="3">均衡</option><option value="4">强压</option></select><span id="compressionValue" data-short="默" aria-hidden="true">默认</span></label>
<label class="toolbar-select"><select id="outputCompression" class="format-select" title="压缩模式" aria-label="压缩模式" disabled><option value="0">原始</option><option value="1">无损</option><option value="2">轻压</option><option value="3">均衡</option><option value="4">强压</option></select><span id="compressionValue" data-short="原" aria-hidden="true">原始</span></label>
<button id="exportPageButton" class="button" type="button" data-short="单" title="导出单页" aria-label="导出单页" disabled>单页</button>
<button id="exportButton" class="button" type="button" data-short="多" title="导出文档" aria-label="导出文档" aria-haspopup="dialog" aria-controls="exportPanel" disabled>文档</button>
</div>
@@ -184,7 +184,7 @@
<button id="convertAdd" class="button" type="button">添加</button><button id="convertClear" class="button" type="button">清空</button>
<div class="convert-options">
<div class="form-row"><label for="convertFormat">格式</label><select id="convertFormat" required></select></div>
<div class="form-row"><label for="convertCompression">压缩</label><select id="convertCompression"><option value="0">默认</option><option value="1">无损</option><option value="2">轻压</option><option value="3">均衡</option><option value="4">强压</option></select></div>
<div class="form-row"><label for="convertCompression">压缩</label><select id="convertCompression"><option value="0">原始</option><option value="1">无损</option><option value="2">轻压</option><option value="3">均衡</option><option value="4">强压</option></select></div>
<div id="convertFontRow" class="form-row" hidden><label for="convertFontMode">字体</label><select id="convertFontMode"><option value="0">保持</option><option value="1">内嵌</option><option value="2">外部</option></select></div>
<div id="convertDPIRow" class="form-row" hidden><label for="convertDPI">精度</label><select id="convertDPI"><option value="96">96 dpi</option><option value="150">150 dpi</option><option value="300" selected>300 dpi</option><option value="600">600 dpi</option></select></div>
<div class="form-row"><label for="convertDestination">保存</label><select id="convertDestination"><option value="directory">目录</option><option value="archive">打包</option></select></div>
+1 -1
View File
@@ -1381,7 +1381,7 @@ document.addEventListener("selectionchange", syncSelection);
el.exportFormat.addEventListener("change", () => updateDPIControl());
el.outputCompression.addEventListener("change", () => {
el.compressionValue.textContent = el.outputCompression.selectedOptions[0].textContent;
el.compressionValue.dataset.short = ["默", "无", "轻", "均", "强"][Number(el.outputCompression.value)];
el.compressionValue.dataset.short = ["原", "无", "轻", "均", "强"][Number(el.outputCompression.value)];
});
el.exportPageButton.addEventListener("click", () => exportFile(false));
el.exportButton.addEventListener("click", () => openExportPanel());
+1 -1
View File
@@ -10,7 +10,7 @@ require (
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893
github.com/xiaoqidun/jbig2 v1.1.4
github.com/xiaoqidun/pdfgo v0.0.0-20261010014421-57af90cea2c6
github.com/xiaoqidun/pdfgo v0.0.0-20261010042215-c8c66c83f031
golang.org/x/crypto v0.58.0
golang.org/x/image v0.47.0
golang.org/x/text v0.43.0
+2 -2
View File
@@ -62,8 +62,8 @@ github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893 h1:jmQlBO7R+bcimOM
github.com/xiaoqidun/j2kgo v0.0.0-20261009063910-4865a21ef893/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
github.com/xiaoqidun/jbig2 v1.1.4 h1:LF6J1si4JY+TszTh9+m4KAg9/ba0JjfXoGtQTQC2S8c=
github.com/xiaoqidun/jbig2 v1.1.4/go.mod h1:9jromqVwMqlptUDEt6Yip8vqYA5I1YjhG7FgMl/Z2dA=
github.com/xiaoqidun/pdfgo v0.0.0-20261010014421-57af90cea2c6 h1:APuDHlsRUfDMkV7+ONj2FwRFRGkO+KzbHqBoWh2aG54=
github.com/xiaoqidun/pdfgo v0.0.0-20261010014421-57af90cea2c6/go.mod h1:fXz1emtF1YCpcIv+fglDZstjkHxjS2l3G4DVMgUzVnk=
github.com/xiaoqidun/pdfgo v0.0.0-20261010042215-c8c66c83f031 h1:FV9MPGAQ08+AGYMPKesJWl42eLL5TPU+4niOlY1sVTY=
github.com/xiaoqidun/pdfgo v0.0.0-20261010042215-c8c66c83f031/go.mod h1:fXz1emtF1YCpcIv+fglDZstjkHxjS2l3G4DVMgUzVnk=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+2 -2
View File
@@ -46,13 +46,13 @@ const (
// outputOptimizationBufferLimit 限制压缩时的单个输出缓冲,单位为字节
const outputOptimizationBufferLimit = 64 << 20
// CompressionMode 选择默认、无损或有损输出策略
// CompressionMode 选择原始、无损或有损输出策略
type CompressionMode = pdfgo.CompressionMode
// CompressionLevel 选择轻压、均衡或强压,仅用于有损模式
type CompressionLevel = pdfgo.CompressionLevel
// CompressionOptions 配置输出压缩,默认模式沿用现有输出策略,不额外优化
// CompressionOptions 配置输出压缩,原始模式沿用现有输出策略,不额外优化
// Quality和MaxDPI为零时按Level使用预设,不改变页面尺寸或整页导出精度
// 无损表示不引入额外损失,不保证跨格式转换可逆;有损不栅格化文字和矢量
type CompressionOptions = pdfgo.CompressionOptions
+1 -1
View File
@@ -108,7 +108,7 @@ func OutputFormats() []OutputFormat {
}
// Convert 将PDF或OFD转换为指定格式,不关闭调用方的输入和输出
// 默认模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本
// 原始模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本
// 输入按需随机读取,输出分块写出;解析对象及后端可能缓存资源,不保证恒定内存
// 出错时报告保留已有诊断,调用方应丢弃未完成输出;只有返回nil才表示转换成功
// 入参: ctx 取消上下文, source 源数据, size 源字节数, output 输出流, options 转换选项
+3 -2
View File
@@ -25,6 +25,7 @@ import (
// 来源修改或删除不影响快照,不用于跨文档传输
type AnnotationSelection struct {
editor *Editor
document editorDocumentIdentity
data [][]byte
resources []string
states map[string]map[string]editorCompositeState
@@ -38,7 +39,7 @@ func (e *Editor) CaptureAnnotations(page int, ids []string) (*AnnotationSelectio
if err != nil {
return nil, err
}
selection := &AnnotationSelection{editor: e, states: make(map[string]map[string]editorCompositeState)}
selection := &AnnotationSelection{editor: e, document: e.documentIdentity(), states: make(map[string]map[string]editorCompositeState)}
seen := make(map[string]bool)
for _, id := range ids {
if seen[id] {
@@ -73,7 +74,7 @@ func (e *Editor) CaptureAnnotations(page int, ids []string) (*AnnotationSelectio
// 入参: page 目标页面索引, selection 当前编辑器快照, dx 横向位移,单位为毫米, dy 纵向位移,单位为毫米
// 返回: []string 新注解标识, error 错误信息
func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx, dy float64) ([]string, error) {
if selection == nil || selection.editor != e || !finite(dx) || !finite(dy) {
if selection == nil || selection.editor != e || !e.documentIdentity().matches(selection.document) || !finite(dx) || !finite(dy) {
return nil, fmt.Errorf("invalid annotation selection or offset")
}
var result []string
+7 -6
View File
@@ -27,6 +27,7 @@ import (
// 父路径的祖先成员增删或排序后需重新捕获,避免使用已失效的序号路径
type CompositeSelection struct {
editor *Editor
document editorDocumentIdentity
page string
path ObjectPath
references int
@@ -70,7 +71,7 @@ func (e *Editor) CaptureCompositeObjects(page int, path ObjectPath, indexes []in
slices.Sort(indexes)
var owner *editorCompositeNode
var container *editorXML
selection := &CompositeSelection{editor: e, page: e.pages[page].ID, path: ObjectPath{ID: path.ID, Annotation: path.Annotation, Children: slices.Clone(path.Children)}}
selection := &CompositeSelection{editor: e, document: e.documentIdentity(), page: e.pages[page].ID, path: ObjectPath{ID: path.ID, Annotation: path.Annotation, Children: slices.Clone(path.Children)}}
if path.Annotation == "" {
selection.source = e.objectOrigin(path.ID).page
} else if e.source != nil {
@@ -112,8 +113,8 @@ func (e *Editor) CaptureCompositeObjects(page int, path ObjectPath, indexes []in
// 入参: page 页面索引, path 父复合路径, selection 捕获的快照, dx 页面横向位移, dy 页面纵向位移
// 返回: []int 新成员序号, error 错误信息
func (e *Editor) PasteCompositeObjects(page int, path ObjectPath, selection *CompositeSelection, dx, dy float64) ([]int, error) {
if selection == nil || selection.editor != e {
return nil, fmt.Errorf("composite selection belongs to another editor")
if selection == nil || selection.editor != e || !e.documentIdentity().matches(selection.document) {
return nil, fmt.Errorf("composite selection belongs to another document or editor")
}
if !finite(dx) || !finite(dy) {
return nil, fmt.Errorf("paste requires finite offsets")
@@ -198,8 +199,8 @@ func (e *Editor) PasteCompositeObjects(page int, path ObjectPath, selection *Com
// 入参: selection 独立内部快照, paste 目标范围粘贴操作
// 返回: error 错误信息
func (e *Editor) pasteCompositeSelection(selection *CompositeSelection, paste func([]GraphicObject) error) (err error) {
if selection == nil || selection.editor != e {
return fmt.Errorf("composite selection belongs to another editor")
if selection == nil || selection.editor != e || !e.documentIdentity().matches(selection.document) {
return fmt.Errorf("composite selection belongs to another document or editor")
}
count, maximum := len(e.resources), e.maxID
ready := e.source != nil && e.source.idsReady
@@ -324,7 +325,7 @@ func (e *Editor) pasteCompositeSelection(selection *CompositeSelection, paste fu
object.CompositeGraphicUnit.states = maps.Clone(copy.states)
copy.object.CompositeGraphicUnit.states = object.CompositeGraphicUnit.states
}
object.origin = &editorObjectOrigin{editor: e, page: selection.source, data: copy.data, node: copy.node, object: copy.object}
object.origin = &editorObjectOrigin{editor: e, document: e.documentIdentity(), page: selection.source, data: copy.data, node: copy.node, object: copy.object}
objects = append(objects, object)
}
return paste(objects)
+2 -2
View File
@@ -201,7 +201,7 @@ func (e *Editor) ungroupCompositeNode(renderer *Renderer, node *editorCompositeN
return fmt.Errorf("group member cannot be expanded")
}
}
return e.pasteCompositeSelection(&CompositeSelection{editor: e, nodes: members}, func(objects []GraphicObject) error {
return e.pasteCompositeSelection(&CompositeSelection{editor: e, document: e.documentIdentity(), nodes: members}, func(objects []GraphicObject) error {
ids := make(map[string]string)
if copy.ref != nil {
for _, object := range objects {
@@ -244,7 +244,7 @@ func (e *Editor) ungroupCompositeNode(renderer *Renderer, node *editorCompositeN
member.object.state = states[editorObjectID(member.object)]
member.object.TextObject.layout = member.object.state.layout
member.object.CompositeGraphicUnit.states = states
member.object.origin = &editorObjectOrigin{editor: e, data: member.data, node: member.node, object: member.object}
member.object.origin = &editorObjectOrigin{editor: e, document: e.documentIdentity(), data: member.data, node: member.node, object: member.object}
objects[i] = member.object
}
return replace(objects)
+1 -1
View File
@@ -37,7 +37,7 @@ func (e *Editor) copiedObjectStyle(object GraphicObject, layer string) (GraphicO
// 返回: *editorObjectOrigin 原文来源
func (e *Editor) snapshotOrigin(object GraphicObject) *editorObjectOrigin {
if object.origin != nil {
if object.origin.editor == e || object.origin.page != nil && e.source != nil && e.source.pages[object.origin.page.ref.ID] == object.origin.page {
if object.origin.editor == e && e.documentIdentity().matches(object.origin.document) || object.origin.page != nil && e.source != nil && e.source.pages[object.origin.page.ref.ID] == object.origin.page {
return object.origin
}
return nil
+32 -7
View File
@@ -27,6 +27,15 @@ import (
"strings"
)
// editorDocumentIdentity 标记编辑状态所属的文档,不持有页面或资源数据
type editorDocumentIdentity struct {
root string
id string
fresh bool
single bool
fromNew bool
}
// DocumentCount 获取编辑包内的文档数量,新建编辑器包含一个当前文档
// 返回: int 文档数量
func (e *Editor) DocumentCount() int {
@@ -329,15 +338,31 @@ func (e *Editor) documentRoot() string {
// 入参: other 编辑快照
// 返回: bool 是否为同一文档
func (e *Editor) sameDocument(other *Editor) bool {
if e.source == nil || other.source == nil {
if e.source == nil && other.source == nil {
return e.documentIdentity().matches(other.documentIdentity())
}
// documentIdentity 获取当前文档的入口与原始标识,文档排序不改变身份
// 返回: editorDocumentIdentity 文档身份
func (e *Editor) documentIdentity() editorDocumentIdentity {
if e.source == nil {
return editorDocumentIdentity{id: e.Info.DocID, fresh: true}
}
return editorDocumentIdentity{root: e.documentRoot(), id: e.source.info.DocID, single: e.DocumentCount() == 1, fromNew: e.source.fromNew}
}
// matches 判断两个编辑状态是否属于同一文档,允许新文档首次建立包结构
// 入参: other 另一编辑状态的文档身份
// 返回: bool 是否为同一文档
func (identity editorDocumentIdentity) matches(other editorDocumentIdentity) bool {
if identity.fresh || other.fresh {
if identity.fresh && other.fresh {
return true
}
fresh, packaged := e, other
if fresh.source != nil {
fresh, packaged = other, e
fresh, packaged := identity, other
if !fresh.fresh {
fresh, packaged = other, identity
}
return len(packaged.source.reader.OFD.DocBody) == 1 && (packaged.source.fromNew || fresh.Info.DocID != "" && fresh.Info.DocID == packaged.source.info.DocID)
return packaged.single && (packaged.fromNew || fresh.id != "" && fresh.id == packaged.id)
}
return e.documentRoot() == other.documentRoot() && e.source.info.DocID == other.source.info.DocID
return identity.root == other.root && identity.id == other.id
}
+41 -19
View File
@@ -91,6 +91,11 @@ func (e *Editor) changeFont(ctx context.Context, id string, mode FontMode, file
if err != nil || !changed {
return err
}
if mode == FontExternal {
if err := pruneExternalFontFiles(ctx, reader, scan.fonts[editorResourceID(id)].location); err != nil {
return err
}
}
return e.commitFontSnapshot(ctx, reader)
}
@@ -133,6 +138,7 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
return nil, err
}
var report []FontChange
var externalFiles []string
changed := false
for document := range reader.DocumentCount() {
if err := progress("scan", document, reader.DocumentCount()); err != nil {
@@ -172,6 +178,9 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
}
if item.Changed {
changed = true
if mode == FontExternal {
externalFiles = append(externalFiles, declaration.location)
}
scan, err = scanEditorFonts(ctx, reader, document)
if err != nil {
return nil, err
@@ -186,6 +195,9 @@ func (e *Editor) ProcessFontsWithOptions(ctx context.Context, options FontProces
if err := progress("commit", 0, 1); err != nil {
return nil, err
}
if err := pruneExternalFontFiles(ctx, reader, externalFiles...); err != nil {
return nil, err
}
err = e.commitFontSnapshot(ctx, reader)
}
return report, err
@@ -343,11 +355,6 @@ func (e *Editor) changeFontSnapshot(ctx context.Context, scan *editorFontDocumen
if err := working.initRoot(); err != nil {
return false, err
}
if mode == FontExternal {
if err := pruneExternalFontFiles(ctx, &working, declaration.location); err != nil {
return false, err
}
}
*scan.reader = working
return true, nil
}
@@ -502,6 +509,9 @@ func (e *Editor) commitFontSnapshot(ctx context.Context, reader *Reader) error {
if err != nil {
return err
}
if err := ctx.Err(); err != nil {
return err
}
before := e.transactionSnapshot()
e.restoreFontSnapshot(*next)
if change := e.recordChange(); change != nil {
@@ -525,11 +535,22 @@ func (e *Editor) restoreFontSnapshot(state Editor) {
*e = state
}
// pruneExternalFontFiles 保留全包仍引用的字体,未确认的扩展引用使清理保持保守
// 入参: ctx 取消上下文, reader 包快照, candidate 原字体路径
// pruneExternalFontFiles 清理全包不再引用的字体文件,遇到无法识别的扩展时保留资源
// 入参: ctx 取消上下文, reader 包快照, candidates 待清理的字体路径
// 返回: error 取消或读取错误
func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidate string) error {
used := make(map[string]bool)
func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidates ...string) error {
if err := ctx.Err(); err != nil {
return err
}
removed := make(map[string]bool, len(candidates))
for _, candidate := range candidates {
if key := strings.ToLower(cleanPackagePath(candidate)); key != "" {
removed[key] = true
}
}
if len(removed) == 0 {
return nil
}
for i := range reader.DocumentCount() {
files, err := reader.documentFiles(ctx, i)
if err != nil {
@@ -538,13 +559,13 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidate strin
}
return nil
}
maps.Copy(used, files)
for name := range files {
delete(removed, name)
}
removed := make(map[string]bool)
key := strings.ToLower(cleanPackagePath(candidate))
if used[key] {
if len(removed) == 0 {
return nil
}
}
for _, name := range reader.fileNamesFold {
if err := ctx.Err(); err != nil {
return err
@@ -574,20 +595,21 @@ func pruneExternalFontFiles(ctx context.Context, reader *Reader, candidate strin
return true
}
}
if node.matchesOFD("FontFile") && strings.EqualFold(resolveResourcePath(name, base, strings.TrimSpace(editorImportText(data, node))), candidate) {
return true
if node.matchesOFD("FontFile") {
location := resolveResourcePath(name, base, strings.TrimSpace(editorImportText(data, node)))
delete(removed, strings.ToLower(cleanPackagePath(location)))
}
return slices.ContainsFunc(node.children, retains)
}
if retains(root) {
if retains(root) || len(removed) == 0 {
return nil
}
}
if !used[key] {
removed[key] = true
if err := ctx.Err(); err != nil {
return err
}
for name := range reader.files {
if removed[strings.ToLower(name)] {
if removed[strings.ToLower(cleanPackagePath(name))] {
delete(reader.files, name)
}
}
+1 -1
View File
@@ -216,7 +216,7 @@ func (e *Editor) ImportObjects(source *Editor, sourcePage int, ids []string, pag
imported[i].TextObject.layout = objects[i].TextObject.layout
imported[i].state = objects[i].state
imported[i].CompositeGraphicUnit.states = remapCompositeStates(objects[i].CompositeGraphicUnit.states, migration.ids)
imported[i].origin = &editorObjectOrigin{editor: editor, data: data, node: root.child("Layer").children[0], object: object}
imported[i].origin = &editorObjectOrigin{editor: editor, document: editor.documentIdentity(), data: data, node: root.child("Layer").children[0], object: object}
}
copied, err = editor.CopyObjects(page, imported, options.DX, options.DY)
return err
+2
View File
@@ -48,6 +48,7 @@ type editorSource struct {
// editorObjectOrigin 保留原对象的XML语义,供复制和局部更新复用
type editorObjectOrigin struct {
editor *Editor
document editorDocumentIdentity
page *editorSourcePage
data []byte
node *editorXML
@@ -613,6 +614,7 @@ func (e *Editor) setObjectOrigin(id string, origin *editorObjectOrigin) {
if origin != nil && origin.page == nil && origin.editor == nil {
copy := *origin
copy.editor = e
copy.document = e.documentIdentity()
origin = &copy
}
e.origins[id] = origin
+5 -4
View File
@@ -20,6 +20,7 @@ import "fmt"
// 来源修改、删除或撤销不改变快照,不用于跨文档传输
type ObjectSelection struct {
editor *Editor
document editorDocumentIdentity
objects []GraphicObject
annotations *AnnotationSelection
}
@@ -32,7 +33,7 @@ func (e *Editor) CaptureSelection(page int, objects, annotations []string) (*Obj
if err != nil {
return nil, err
}
selection := &ObjectSelection{editor: e, objects: items}
selection := &ObjectSelection{editor: e, document: e.documentIdentity(), objects: items}
if len(annotations) != 0 {
selection.annotations, err = e.CaptureAnnotations(page, annotations)
if err != nil {
@@ -46,7 +47,7 @@ func (e *Editor) CaptureSelection(page int, objects, annotations []string) (*Obj
// 入参: page 目标页面索引, selection 当前编辑器快照, dx 横向位移,单位为毫米, dy 纵向位移,单位为毫米
// 返回: []string 新正文标识, []string 新注解标识, error 标识、资源或编辑错误
func (e *Editor) PasteSelection(page int, selection *ObjectSelection, dx, dy float64) ([]string, []string, error) {
if selection == nil || selection.editor != e || !finite(dx) || !finite(dy) {
if selection == nil || selection.editor != e || !e.documentIdentity().matches(selection.document) || !finite(dx) || !finite(dy) {
return nil, nil, fmt.Errorf("invalid object selection or offset")
}
var objects, annotations []string
@@ -68,8 +69,8 @@ func (e *Editor) PasteSelection(page int, selection *ObjectSelection, dx, dy flo
// 入参: page 目标页面索引, path 父复合路径, selection 当前编辑器快照, dx 横向位移,单位为毫米, dy 纵向位移,单位为毫米
// 返回: []int 新成员序号, error 选区、资源或编辑错误
func (e *Editor) PasteSelectionToComposite(page int, path ObjectPath, selection *ObjectSelection, dx, dy float64) ([]int, error) {
if selection == nil || selection.editor != e {
return nil, fmt.Errorf("object selection belongs to another editor")
if selection == nil || selection.editor != e || !e.documentIdentity().matches(selection.document) {
return nil, fmt.Errorf("object selection belongs to another document or editor")
}
if selection.annotations != nil {
return nil, fmt.Errorf("annotations cannot be nested in a page object")
+1 -1
View File
@@ -207,7 +207,7 @@ func (e *Editor) SetObjectDrawParam(page int, ids []string, id string) error {
if err != nil {
return err
}
updated := &editorObjectOrigin{editor: e, data: data, node: root, object: next}
updated := &editorObjectOrigin{editor: e, document: e.documentIdentity(), data: data, node: root, object: next}
if origin != nil {
updated.page, root.parent = origin.page, origin.node.parent
} else {
+1 -1
View File
@@ -37,7 +37,7 @@ var ErrPDFPassword = pdfgo.ErrPassword
// ResolveFile读取PDF引用的外部媒体、附件和页面,默认不访问网络或本地路径
// ResolveReference可提供已解密的引用页面,目标阅读器由调用方维护
// Halftones提供只读设备命名网屏,所属阅读器由调用方维护,连续色调输出不网屏化
// Compression配置转换输出,有损模式按图片实际尺寸降采样,默认及无损保持完整采样
// Compression配置转换输出,有损模式按图片实际尺寸降采样,原始及无损模式保持完整采样
type PDFImportOptions struct {
Password []byte
PasswordUTF8 bool