feat(编辑能力): 优化编辑响应与资源复用
build.yaml / build (push) Successful in 45s

This commit is contained in:
xiaoqidun committed 2026-10-26 19:28:10 +08:00
1 parent 413d41c31d
commit f1864952ad
10 files changed
+450 -97

No files matched your search

+38 -1
View File
@@ -4828,6 +4828,9 @@ async function changeDocument(name, item, ...args) {
openSeq = advanceDocumentSequence(); openSeq = advanceDocumentSequence();
if (changingFont) { if (changingFont) {
await refreshFontBinding(doc, openSeq); await refreshFontBinding(doc, openSeq);
} else if (restoring && !doc.changes?.all && doc.changes?.pages?.length === 1 && doc.changes.pages[0] === state.pageIndex && (restoredIndex < 0 || restoredIndex === state.pageIndex)) {
if (location) canvasEditor.pendingSelection = {index:state.pageIndex, ids:location.ids};
await refreshEditorPage(doc, state.pageIndex, openSeq, clearSelection);
} else if (item || name === "ofdgoPasteObjects" || inserting || editingAnnotation) { } else if (item || name === "ofdgoPasteObjects" || inserting || editingAnnotation) {
await refreshEditorPage(doc, item ? item.index : args[0], openSeq, clearSelection); await refreshEditorPage(doc, item ? item.index : args[0], openSeq, clearSelection);
} else { } else {
@@ -4960,7 +4963,7 @@ async function refreshFontBinding(doc, openSeq) {
async function refreshEditorPage(doc, index, openSeq, clearSelection = false) { async function refreshEditorPage(doc, index, openSeq, clearSelection = false) {
state.doc = doc; state.doc = doc;
resetSearch(); const search = refreshSearchPages([index], openSeq);
state.documentSelection = null; state.documentSelection = null;
pageView.resetPageLoading(); pageView.resetPageLoading();
try { try {
@@ -4997,6 +5000,7 @@ async function refreshEditorPage(doc, index, openSeq, clearSelection = false) {
renderMeta(true); renderMeta(true);
updateControls(); updateControls();
loadDocumentDetails(openSeq); loadDocumentDetails(openSeq);
await search;
for (const shell of el.svgHost.children) pageView.observeFlowPage(shell); for (const shell of el.svgHost.children) pageView.observeFlowPage(shell);
for (const button of el.pageList.children) pageView.observeThumbnail(button, Number(button.dataset.pageIndex), openSeq); for (const button of el.pageList.children) pageView.observeThumbnail(button, Number(button.dataset.pageIndex), openSeq);
} }
@@ -10771,6 +10775,39 @@ function resetSearch(clearInput = true) {
clearSearchHighlights(); clearSearchHighlights();
} }
async function refreshSearchPages(pages, openSeq) {
const tab = documentTab(), target = tab.state, query = target.searchQuery;
if (!query || target.searchRunning) { resetSearch(false); return; }
const seq = ++target.searchSeq;
target.searchRunning = true;
const selected = target.searchMatches[target.searchIndex];
try {
const matches = target.searchMatches.filter(match => !pages.includes(match.page));
for (const page of pages) {
const result = await callTabWASM(tab, "ofdgoSearchPage", page, query);
if (tab.closed || target.openSeq !== openSeq || target.searchSeq !== seq) return;
for (const match of result || []) matches.push({...match, page});
}
matches.sort((a,b) => a.page - b.page);
target.searchMatches = matches;
target.searchIndex = selected ? matches.indexOf(selected) : -1;
if (isForegroundDocument(tab)) {
el.searchResults.replaceChildren();
clearSearchHighlights();
el.searchStatus.textContent = matches.length ? "搜索完成" : "暂无结果";
renderSearchResults();
if (target.searchIndex >= 0) renderSearchHighlights(matches[target.searchIndex].page);
}
} catch (err) {
if (target.openSeq === openSeq && target.searchSeq === seq) {
target.searchQuery = "";
if (isForegroundDocument(tab)) { resetSearch(false); showError(err, false); }
}
} finally {
if (target.searchSeq === seq) target.searchRunning = false;
}
}
async function searchDocument() { async function searchDocument() {
if (!state.doc || document.body.hasAttribute("data-busy")) { if (!state.doc || document.body.hasAttribute("data-busy")) {
return; return;
+26 -6
View File
@@ -365,11 +365,12 @@ type editorClipboard struct {
// editorInfo 编辑文档信息与操作状态 // editorInfo 编辑文档信息与操作状态
type editorInfo struct { type editorInfo struct {
DocumentInfo DocumentInfo
Revision uint64 `json:"revision"` Revision uint64 `json:"revision"`
CanUndo bool `json:"canUndo"` CanUndo bool `json:"canUndo"`
CanRedo bool `json:"canRedo"` CanRedo bool `json:"canRedo"`
PageCapabilities []uint `json:"pageCapabilities"` PageCapabilities []uint `json:"pageCapabilities"`
EditWarnings []string `json:"editWarnings,omitempty"` EditWarnings []string `json:"editWarnings,omitempty"`
Changes ofdgo.EditorChanges `json:"changes"`
} }
// editorPageInfo 页面操作结果与目标页面 // editorPageInfo 页面操作结果与目标页面
@@ -3203,7 +3204,7 @@ func (e *businessEngine) encodeResult(result apiResult) string {
// editorSummary 获取当前编辑文档状态 // editorSummary 获取当前编辑文档状态
// 返回: editorInfo 文档信息 // 返回: editorInfo 文档信息
func (e *businessEngine) editorSummary() editorInfo { func (e *businessEngine) editorSummary() editorInfo {
info := editorInfo{DocumentInfo: e.currentSession.Summary(), Revision: e.currentEditor.Revision(), CanUndo: e.currentEditor.CanUndo(), CanRedo: e.currentEditor.CanRedo()} info := editorInfo{DocumentInfo: e.currentSession.Summary(), Revision: e.currentEditor.Revision(), CanUndo: e.currentEditor.CanUndo(), CanRedo: e.currentEditor.CanRedo(), Changes: e.currentEditor.Changes()}
if !e.currentSession.doc.Permissions.Edit { if !e.currentSession.doc.Permissions.Edit {
info.EditWarnings = append(info.EditWarnings, "原文件声明不允许编辑") info.EditWarnings = append(info.EditWarnings, "原文件声明不允许编辑")
} }
@@ -6741,6 +6742,25 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) (
session.Renderer.SetFontFS(session.fontFS) session.Renderer.SetFontFS(session.fontFS)
editor.SetFontFS(session.fontFS) editor.SetFontFS(session.fontFS)
} }
changes := editor.Changes()
if editor == e.currentEditor && !changes.All && len(session.boxCache) == len(e.currentSession.boxCache) {
changed := changes.Pages
for index, page := range e.currentSession.pageCache {
if !slices.Contains(changed, index) {
session.pageCache[index] = page
}
}
for _, index := range e.currentSession.textOrder {
if !slices.Contains(changed, index) {
session.cachePageText(index, e.currentSession.textCache[index])
}
}
for index, box := range e.currentSession.boxCache {
if !slices.Contains(changed, index) {
session.boxCache[index] = box
}
}
}
if editor == e.currentEditor { if editor == e.currentEditor {
session.input, e.currentSession.input = e.currentSession.input, nil session.input, e.currentSession.input = e.currentSession.input, nil
} }
+1
View File
@@ -70,6 +70,7 @@ type outputOptimization struct {
protected bool protected bool
completed int completed int
deflater *flate.Writer deflater *flate.Writer
resources *editorResourceSelection
} }
// outputCompressionBuffer 限制候选ZIP编码,不能缩小时终止本次压缩 // outputCompressionBuffer 限制候选ZIP编码,不能缩小时终止本次压缩
+37 -39
View File
@@ -46,38 +46,41 @@ const editorPathValidationLimit = 1024
// 阶段可重复,准备完成不代表保存成功 // 阶段可重复,准备完成不代表保存成功
// 回调不可重入修改编辑器,可返回context.Canceled等调用方停止原因 // 回调不可重入修改编辑器,可返回context.Canceled等调用方停止原因
type Editor struct { type Editor struct {
OnWriteProgress func(stage string, completed, total int) error OnWriteProgress func(stage string, completed, total int) error
Info DocInfo Info DocInfo
pages []PageContent pages []PageContent
resources []editorResource resources []editorResource
runtimeResources *Resources runtimeResources *Resources
fonts map[string]*FontResource fonts map[string]*FontResource
fontRegistration editorFontRegistration fontRegistration editorFontRegistration
fontDirs []string fontDirs []string
fontFS []fs.FS fontFS []fs.FS
fontRenderer *Renderer fontRenderer *Renderer
fontSourcesCache *fontSourceCache fontSourcesCache *fontSourceCache
fontMetrics map[string]FontMetrics fontMetrics map[string]FontMetrics
backends RenderBackends backends RenderBackends
images map[string]image.Point images map[string]image.Point
resourceID map[editorResourceKey]string resourceID map[editorResourceKey]string
maxID int maxID int
history []editorChange history []editorChange
historyIndex int historyIndex int
historyLimit int historyLimit int
revision uint64 revision uint64
serial uint64 serial uint64
source *editorSource source *editorSource
origins map[string]*editorObjectOrigin origins map[string]*editorObjectOrigin
removedPages map[string]bool removedPages map[string]bool
structureRemaps map[string][]map[string]string structureRemaps map[string][]map[string]string
outlines []byte outlines []byte
encryption *encryptionState encryption *encryptionState
output *outputOptimization output *outputOptimization
saveTarget Output saveTarget Output
snapshotArchive *editorSnapshotArchive snapshotArchive *editorSnapshotArchive
validatedPaths *renderCache[string, bool] validatedPaths *renderCache[string, bool]
richIndex *richTextIndex changes EditorChanges
transactionChanges *EditorChanges
objectIndex *editorObjectIndex
richIndex *richTextIndex
} }
// editorResource 文档内嵌资源 // editorResource 文档内嵌资源
@@ -892,13 +895,8 @@ func (e *Editor) findObject(page int, id string) (*Layer, int, error) {
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
for i := range content.Content.Layer { if position, ok := e.objectPositions(content)[id]; ok {
layer := &content.Content.Layer[i] return &content.Content.Layer[position.layer], position.index, nil
for index, object := range layer.Objects {
if editorObjectID(object) == id && id != "" {
return layer, index, nil
}
}
} }
return nil, 0, fmt.Errorf("object %q not found on page %d", id, page) return nil, 0, fmt.Errorf("object %q not found on page %d", id, page)
} }
+50 -22
View File
@@ -30,6 +30,36 @@ type editorObjectPosition struct {
index int index int
} }
// editorObjectIndex 复用最近页面的只读对象位置,图层容器替换后重新建立
type editorObjectIndex struct {
layers []Layer
positions map[string]editorObjectPosition
}
// objectPositions 获取页面的对象位置索引,重复编号保留第一个匹配
// 入参: page 内部只读页面
// 返回: map[string]editorObjectPosition 只读索引
func (e *Editor) objectPositions(page *PageContent) map[string]editorObjectPosition {
layers := page.Content.Layer
if len(layers) == 0 {
return nil
}
if cached := e.objectIndex; cached != nil && len(cached.layers) == len(layers) && &cached.layers[0] == &layers[0] {
return cached.positions
}
positions := make(map[string]editorObjectPosition)
for i, layer := range layers {
for j, object := range layer.Objects {
id := editorObjectID(object)
if _, exists := positions[id]; id != "" && !exists {
positions[id] = editorObjectPosition{i, j}
}
}
}
e.objectIndex = &editorObjectIndex{layers: layers, positions: positions}
return positions
}
// ObjectPosition 对象在图层或页块直接成员中的位置,Index从0开始,Count不含嵌套页块的对象 // ObjectPosition 对象在图层或页块直接成员中的位置,Index从0开始,Count不含嵌套页块的对象
type ObjectPosition struct { type ObjectPosition struct {
Layer string Layer string
@@ -709,7 +739,7 @@ func (e *Editor) replaceLayers(page int, layers []Layer) {
func (e *Editor) replacePreparedLayers(page int, after []Layer) { func (e *Editor) replacePreparedLayers(page int, after []Layer) {
if e.historyLimit == 0 { if e.historyLimit == 0 {
e.pages[page].Content.Layer = after e.pages[page].Content.Layer = after
e.recordChange() e.recordChange(page)
return return
} }
before := copyEditorPage(e.pages[page]) before := copyEditorPage(e.pages[page])
@@ -738,7 +768,7 @@ func (e *Editor) replacePreparedLayers(page int, after []Layer) {
e.pages[page].Content.Layer = copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.Layer e.pages[page].Content.Layer = copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.Layer
} }
apply(e, after) apply(e, after)
if change := e.recordChange(); change != nil { if change := e.recordChange(page); change != nil {
change.undo = func(e *Editor) { apply(e, before.Content.Layer) } change.undo = func(e *Editor) { apply(e, before.Content.Layer) }
change.redo = func(e *Editor) { apply(e, after) } change.redo = func(e *Editor) { apply(e, after) }
} }
@@ -835,6 +865,15 @@ func (e *Editor) updateObjectOrigins(page int, objects []GraphicObject, preserve
return nil return nil
} }
apply := func(e *Editor, objects []GraphicObject, origins map[string]*editorObjectOrigin) { apply := func(e *Editor, objects []GraphicObject, origins map[string]*editorObjectOrigin) {
layers := slices.Clone(e.pages[page].Content.Layer)
cloned := make(map[int]bool)
for _, index := range indexes {
if !cloned[index.layer] {
layers[index.layer].Objects = slices.Clone(layers[index.layer].Objects)
cloned[index.layer] = true
}
}
e.pages[page].Content.Layer = layers
for i, index := range indexes { for i, index := range indexes {
e.pages[page].Content.Layer[index.layer].Objects[index.index] = objects[i] e.pages[page].Content.Layer[index.layer].Objects[index.index] = objects[i]
} }
@@ -843,7 +882,7 @@ func (e *Editor) updateObjectOrigins(page int, objects []GraphicObject, preserve
} }
} }
apply(e, after, afterOrigins) apply(e, after, afterOrigins)
if change := e.recordChange(); change != nil { if change := e.recordChange(page); change != nil {
change.undo = func(e *Editor) { apply(e, before, beforeOrigins) } change.undo = func(e *Editor) { apply(e, before, beforeOrigins) }
change.redo = func(e *Editor) { apply(e, after, afterOrigins) } change.redo = func(e *Editor) { apply(e, after, afterOrigins) }
} }
@@ -891,30 +930,19 @@ func (e *Editor) selectedObjects(page int, ids []string) ([]GraphicObject, []edi
if len(ids) == 0 { if len(ids) == 0 {
return objects, indexes, nil return objects, indexes, nil
} }
selected := make(map[string]int, len(ids)) positions := e.objectPositions(content)
selected := make(map[string]bool, len(ids))
for i, id := range ids { for i, id := range ids {
if _, exists := selected[id]; exists { if selected[id] {
return nil, nil, fmt.Errorf("duplicate object %q", id) return nil, nil, fmt.Errorf("duplicate object %q", id)
} }
selected[id] = i selected[id] = true
} position, ok := positions[id]
for i, layer := range content.Content.Layer { if !ok {
for j, object := range layer.Objects {
id := editorObjectID(object)
if index, ok := selected[id]; ok && id != "" {
objects[index] = object
indexes[index] = editorObjectPosition{i, j}
delete(selected, id)
if len(selected) == 0 {
return objects, indexes, nil
}
}
}
}
for _, id := range ids {
if _, missing := selected[id]; missing {
return nil, nil, fmt.Errorf("object %q not found on page %d", id, page) return nil, nil, fmt.Errorf("object %q not found on page %d", id, page)
} }
indexes[i] = position
objects[i] = content.Content.Layer[position.layer].Objects[position.index]
} }
return objects, indexes, nil return objects, indexes, nil
} }
+188
View File
@@ -19,6 +19,7 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"reflect" "reflect"
"slices"
"strconv" "strconv"
"sync" "sync"
) )
@@ -241,6 +242,22 @@ func (e *Editor) copiedLayerStyle(object GraphicObject) string {
// 入参: value 原值 // 入参: value 原值
// 返回: T 独立副本 // 返回: T 独立副本
func cloneEditorData[T any](value T) T { func cloneEditorData[T any](value T) T {
switch source := any(value).(type) {
case GraphicObject:
return any(copyEditorGraphicObject(source)).(T)
case TextObject:
return any(copyEditorTextObject(source)).(T)
case PathObject:
return any(copyEditorPathObject(source)).(T)
case ImageObject:
return any(copyEditorImageObject(source)).(T)
case CompositeGraphicUnit:
return any(copyEditorCompositeGraphicUnit(source)).(T)
case PageContent:
return any(copyEditorPageContent(source)).(T)
case Layer:
return any(copyEditorLayer(source)).(T)
}
source := reflect.ValueOf(value) source := reflect.ValueOf(value)
if !source.IsValid() { if !source.IsValid() {
return value return value
@@ -336,3 +353,174 @@ func editorCloneFields(kind reflect.Type) []int {
actual, _ := editorClonePlans.LoadOrStore(kind, fields) actual, _ := editorClonePlans.LoadOrStore(kind, fields)
return actual.([]int) return actual.([]int)
} }
// copyEditorGraphicObject 复制GraphicObject的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: GraphicObject 独立副本
func copyEditorGraphicObject(value GraphicObject) GraphicObject {
value.TextObject = copyEditorTextObject(value.TextObject)
value.PathObject = copyEditorPathObject(value.PathObject)
value.ImageObject = copyEditorImageObject(value.ImageObject)
value.CompositeGraphicUnit = copyEditorCompositeGraphicUnit(value.CompositeGraphicUnit)
return value
}
// copyEditorTextObject 复制TextObject的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: TextObject 独立副本
func copyEditorTextObject(value TextObject) TextObject {
if value.DashOffset != nil {
copied := *value.DashOffset
value.DashOffset = &copied
}
if value.Alpha != nil {
copied := *value.Alpha
value.Alpha = &copied
}
if value.Visible != nil {
copied := *value.Visible
value.Visible = &copied
}
if value.Fill != nil {
copied := *value.Fill
value.Fill = &copied
}
if value.Stroke != nil {
copied := *value.Stroke
value.Stroke = &copied
}
value.StrokeColor = cloneEditorData(value.StrokeColor)
value.FillColor = cloneEditorData(value.FillColor)
value.CGTransform = slices.Clone(value.CGTransform)
value.TextCode = slices.Clone(value.TextCode)
value.Clips = cloneEditorData(value.Clips)
value.Actions = cloneEditorData(value.Actions)
return value
}
// copyEditorPathObject 复制PathObject的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: PathObject 独立副本
func copyEditorPathObject(value PathObject) PathObject {
if value.DashOffset != nil {
copied := *value.DashOffset
value.DashOffset = &copied
}
if value.Alpha != nil {
copied := *value.Alpha
value.Alpha = &copied
}
if value.Visible != nil {
copied := *value.Visible
value.Visible = &copied
}
if value.Stroke != nil {
copied := *value.Stroke
value.Stroke = &copied
}
if value.Fill != nil {
copied := *value.Fill
value.Fill = &copied
}
value.StrokeColor = cloneEditorData(value.StrokeColor)
value.FillColor = cloneEditorData(value.FillColor)
value.Clips = cloneEditorData(value.Clips)
value.Actions = cloneEditorData(value.Actions)
return value
}
// copyEditorImageObject 复制ImageObject的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: ImageObject 独立副本
func copyEditorImageObject(value ImageObject) ImageObject {
if value.Alpha != nil {
copied := *value.Alpha
value.Alpha = &copied
}
if value.Visible != nil {
copied := *value.Visible
value.Visible = &copied
}
value.Border = cloneEditorData(value.Border)
value.Clips = cloneEditorData(value.Clips)
value.Actions = cloneEditorData(value.Actions)
return value
}
// copyEditorCompositeGraphicUnit 复制CompositeGraphicUnit的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: CompositeGraphicUnit 独立副本
func copyEditorCompositeGraphicUnit(value CompositeGraphicUnit) CompositeGraphicUnit {
if value.Alpha != nil {
copied := *value.Alpha
value.Alpha = &copied
}
if value.Visible != nil {
copied := *value.Visible
value.Visible = &copied
}
value.Objects = slices.Clone(value.Objects)
for i := range value.Objects {
value.Objects[i] = copyEditorGraphicObject(value.Objects[i])
}
value.TextObject = slices.Clone(value.TextObject)
for i := range value.TextObject {
value.TextObject[i] = copyEditorTextObject(value.TextObject[i])
}
value.PathObject = slices.Clone(value.PathObject)
for i := range value.PathObject {
value.PathObject[i] = copyEditorPathObject(value.PathObject[i])
}
value.ImageObject = slices.Clone(value.ImageObject)
for i := range value.ImageObject {
value.ImageObject[i] = copyEditorImageObject(value.ImageObject[i])
}
value.CompositeGraphicUnit = slices.Clone(value.CompositeGraphicUnit)
for i := range value.CompositeGraphicUnit {
value.CompositeGraphicUnit[i] = copyEditorCompositeGraphicUnit(value.CompositeGraphicUnit[i])
}
value.Clips = cloneEditorData(value.Clips)
value.Actions = cloneEditorData(value.Actions)
return value
}
// copyEditorPageContent 复制PageContent的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: PageContent 独立副本
func copyEditorPageContent(value PageContent) PageContent {
value.Template = slices.Clone(value.Template)
value.PageRes = slices.Clone(value.PageRes)
value.Content.Layer = slices.Clone(value.Content.Layer)
for i := range value.Content.Layer {
value.Content.Layer[i] = copyEditorLayer(value.Content.Layer[i])
}
value.Actions = cloneEditorData(value.Actions)
return value
}
// copyEditorLayer 复制Layer的可变字段,保留内部只读状态
// 入参: value 原值
// 返回: Layer 独立副本
func copyEditorLayer(value Layer) Layer {
value.Objects = slices.Clone(value.Objects)
for i := range value.Objects {
value.Objects[i] = copyEditorGraphicObject(value.Objects[i])
}
value.TextObject = slices.Clone(value.TextObject)
for i := range value.TextObject {
value.TextObject[i] = copyEditorTextObject(value.TextObject[i])
}
value.PathObject = slices.Clone(value.PathObject)
for i := range value.PathObject {
value.PathObject[i] = copyEditorPathObject(value.PathObject[i])
}
value.ImageObject = slices.Clone(value.ImageObject)
for i := range value.ImageObject {
value.ImageObject[i] = copyEditorImageObject(value.ImageObject[i])
}
value.CompositeGraphicUnit = slices.Clone(value.CompositeGraphicUnit)
for i := range value.CompositeGraphicUnit {
value.CompositeGraphicUnit[i] = copyEditorCompositeGraphicUnit(value.CompositeGraphicUnit[i])
}
return value
}
+23 -13
View File
@@ -34,8 +34,9 @@ type editorFontUsage struct {
unsafe bool unsafe bool
} }
// editorFontSubset 保存单个新增字体最近一次裁剪结果,不保留历史字形集合 // editorFontSubset 保存单个新增字体及其快照最近一次裁剪结果,不保留历史字形集合
type editorFontSubset struct { type editorFontSubset struct {
source [32]byte
glyphs []uint16 glyphs []uint16
data []byte data []byte
mapped bool mapped bool
@@ -317,13 +318,13 @@ func (e *Editor) subsetSnapshotFonts(ctx context.Context, progress editorProgres
if e.source == nil || e.backends.FontResources == nil { if e.source == nil || e.backends.FontResources == nil {
return nil, ctx.Err() return nil, ctx.Err()
} }
candidates := make(map[string]bool) candidates := make(map[string]int)
for _, resource := range e.resources { for i, resource := range e.resources {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, err return nil, err
} }
if resource.font != nil && resource.font.FontFile != "" && e.source.reader.fileNames[resource.name] { if resource.font != nil && resource.font.FontFile != "" && e.source.reader.fileNames[resource.name] {
candidates[resource.name] = true candidates[resource.name] = i
} }
} }
if len(candidates) == 0 { if len(candidates) == 0 {
@@ -399,15 +400,24 @@ func (e *Editor) subsetSnapshotFonts(ctx context.Context, progress editorProgres
if font.unsafe { if font.unsafe {
continue continue
} }
subset, err := e.backends.FontResources.SubsetFont(data, slices.Sorted(maps.Keys(glyphs)), preserve) ids := slices.Sorted(maps.Keys(glyphs))
if err != nil { checksum := sha256.Sum256(data)
return nil, err resource := &e.resources[candidates[name]]
if resource.subset == nil || resource.subset.source != checksum || resource.subset.mapped != preserve || !slices.Equal(resource.subset.glyphs, ids) {
subset, err := e.backends.FontResources.SubsetFont(data, ids, preserve)
if err != nil {
return nil, err
}
if err := ctx.Err(); err != nil {
return nil, err
}
if len(subset) >= len(data) {
subset = nil
}
resource.subset = &editorFontSubset{source: checksum, glyphs: ids, data: subset, mapped: preserve}
} }
if err := ctx.Err(); err != nil { if len(resource.subset.data) != 0 {
return nil, err result[name] = resource.subset.data
}
if len(subset) != 0 && len(subset) < len(data) {
result[name] = subset
} }
} }
return result, progress.report("fonts", len(candidates), len(candidates)) return result, progress.report("fonts", len(candidates), len(candidates))
@@ -593,7 +603,7 @@ func (e *Editor) subsetNewFonts(progress editorProgress) (map[string][]byte, err
return nil, err return nil, err
} }
resource := &e.resources[i] resource := &e.resources[i]
if resource.font == nil { if resource.font == nil || e.source != nil && e.source.reader.fileNames[resource.name] {
continue continue
} }
usage := used[resource.font.ID] usage := used[resource.font.ID]
+1 -1
View File
@@ -253,7 +253,7 @@ func (e *Editor) replaceGroup(page int, indexes []editorObjectPosition, objects
} }
} }
apply(e, after, origins) apply(e, after, origins)
if change := e.recordChange(); change != nil { if change := e.recordChange(page); change != nil {
change.undo = func(e *Editor) { apply(e, before, previous) } change.undo = func(e *Editor) { apply(e, before, previous) }
change.redo = func(e *Editor) { apply(e, after, origins) } change.redo = func(e *Editor) { apply(e, after, origins) }
} }
+71 -15
View File
@@ -21,10 +21,43 @@ import (
// editorChange 页面或对象操作及其修订标识 // editorChange 页面或对象操作及其修订标识
type editorChange struct { type editorChange struct {
undo func(*Editor) undo func(*Editor)
redo func(*Editor) redo func(*Editor)
before uint64 before uint64
after uint64 after uint64
changes EditorChanges
}
// EditorChanges 描述最近一次修改或历史回放的刷新范围
// All表示文档结构或共享资源变化;否则Pages为受影响的零基页码
// 返回值只覆盖最近一次操作,调用方应在每次操作后读取
type EditorChanges struct {
All bool `json:"all"`
Pages []int `json:"pages,omitempty"`
}
// Changes 获取最近一次有效修改的独立刷新范围
// 返回: EditorChanges 变化范围;未发生修改时为空
func (e *Editor) Changes() EditorChanges {
return EditorChanges{All: e.changes.All, Pages: slices.Clone(e.changes.Pages)}
}
// merge 合并同一事务中的变化范围
// 入参: change 待合并的变化
func (c *EditorChanges) merge(change EditorChanges) {
if c.All {
return
}
if change.All {
c.All = true
c.Pages = nil
return
}
for _, page := range change.Pages {
if !slices.Contains(c.Pages, page) {
c.Pages = append(c.Pages, page)
}
}
} }
// Transaction 将多项修改作为一次原子操作提交,失败时保留原文档及历史 // Transaction 将多项修改作为一次原子操作提交,失败时保留原文档及历史
@@ -37,6 +70,8 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
before.Info = cloneEditorData(e.Info) before.Info = cloneEditorData(e.Info)
next := e.transactionSnapshot() next := e.transactionSnapshot()
next.history, next.historyIndex, next.historyLimit = nil, 0, 0 next.history, next.historyIndex, next.historyLimit = nil, 0, 0
pending := &EditorChanges{}
next.transactionChanges = pending
*e = next *e = next
committed := false committed := false
defer func() { defer func() {
@@ -48,6 +83,11 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
return err return err
} }
changed := e.revision != before.revision changed := e.revision != before.revision
changes := *pending
if e.transactionChanges != pending {
changes = EditorChanges{All: true}
}
e.transactionChanges = before.transactionChanges
e.history, e.historyIndex, e.historyLimit = before.history, before.historyIndex, before.historyLimit e.history, e.historyIndex, e.historyLimit = before.history, before.historyIndex, before.historyLimit
if !sameBackend(e.backends.FontResources, before.backends.FontResources) { if !sameBackend(e.backends.FontResources, before.backends.FontResources) {
e.clearFontResourceCache() e.clearFontResourceCache()
@@ -62,7 +102,7 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
} }
if changed { if changed {
e.serial, e.revision = before.serial, before.revision e.serial, e.revision = before.serial, before.revision
e.recordTransaction(before) e.recordTransaction(before, changes)
} }
committed = true committed = true
return nil return nil
@@ -108,6 +148,7 @@ func (e *Editor) Undo() bool {
e.historyIndex-- e.historyIndex--
change := e.history[e.historyIndex] change := e.history[e.historyIndex]
change.undo(e) change.undo(e)
e.changes, e.objectIndex = change.changes, nil
e.revision = change.before e.revision = change.before
return true return true
} }
@@ -120,6 +161,7 @@ func (e *Editor) Redo() bool {
} }
change := e.history[e.historyIndex] change := e.history[e.historyIndex]
change.redo(e) change.redo(e)
e.changes, e.objectIndex = change.changes, nil
e.historyIndex++ e.historyIndex++
e.revision = change.after e.revision = change.after
return true return true
@@ -132,15 +174,12 @@ func (e *Editor) Revision() uint64 {
return e.revision return e.revision
} }
// transactionSnapshot 复制可变容器,保留只读XML、图元及二进制资源的共享 // transactionSnapshot 复制顶层容器,共享按替换方式更新的页面图层、图元和二进制资源
// 返回: Editor 独立事务状态 // 返回: Editor 独立事务状态
func (e *Editor) transactionSnapshot() Editor { func (e *Editor) transactionSnapshot() Editor {
next := *e next := *e
next.Info = cloneEditorData(e.Info) next.Info = cloneEditorData(e.Info)
next.pages = make([]PageContent, len(e.pages)) next.pages = slices.Clone(e.pages)
for i, page := range e.pages {
next.pages[i] = copyEditorPage(page)
}
next.resources = slices.Clone(e.resources) next.resources = slices.Clone(e.resources)
next.fonts, next.images, next.resourceID = maps.Clone(e.fonts), maps.Clone(e.images), maps.Clone(e.resourceID) next.fonts, next.images, next.resourceID = maps.Clone(e.fonts), maps.Clone(e.images), maps.Clone(e.resourceID)
next.origins = maps.Clone(e.origins) next.origins = maps.Clone(e.origins)
@@ -213,9 +252,13 @@ func (e *Editor) retainDocumentResources(source *Editor) {
} }
// recordTransaction 记录事务快照,在各自资源视图中保留后续注册的资源 // recordTransaction 记录事务快照,在各自资源视图中保留后续注册的资源
// 入参: before 修改前的文档状态 // 入参: before 修改前的文档状态, changes 可选的事务变化范围
func (e *Editor) recordTransaction(before Editor) { func (e *Editor) recordTransaction(before Editor, changes ...EditorChanges) {
if change := e.recordChange(); change != nil { scope := EditorChanges{All: true}
if len(changes) != 0 {
scope = changes[0]
}
if change := e.recordChangeScope(scope); change != nil {
after := e.transactionSnapshot() after := e.transactionSnapshot()
before.history, after.history = nil, nil before.history, after.history = nil, nil
change.undo = func(e *Editor) { change.undo = func(e *Editor) {
@@ -230,10 +273,23 @@ func (e *Editor) recordTransaction(before Editor) {
} }
// recordChange 分配修订标识并为已完成的有效修改预留记录 // recordChange 分配修订标识并为已完成的有效修改预留记录
// 入参: pages 受影响页码,省略表示全局变化
// 返回: *editorChange 记录,未启用历史时为nil // 返回: *editorChange 记录,未启用历史时为nil
func (e *Editor) recordChange() *editorChange { func (e *Editor) recordChange(pages ...int) *editorChange {
return e.recordChangeScope(EditorChanges{All: len(pages) == 0, Pages: slices.Clone(pages)})
}
// recordChangeScope 分配修订并记录变更范围,事务中同步合并
// 入参: scope 变更范围
// 返回: *editorChange 可补充回放操作的记录
func (e *Editor) recordChangeScope(scope EditorChanges) *editorChange {
e.objectIndex = nil
e.changes = scope
if e.transactionChanges != nil {
e.transactionChanges.merge(scope)
}
e.serial++ e.serial++
change := editorChange{before: e.revision, after: e.serial} change := editorChange{before: e.revision, after: e.serial, changes: scope}
e.revision = e.serial e.revision = e.serial
if e.historyLimit == 0 { if e.historyLimit == 0 {
return nil return nil
+15
View File
@@ -791,6 +791,14 @@ func (e *Editor) writePartsWithPages(ctx context.Context, write func(string, []b
return nil return nil
} }
// editorResourceSelection 保存单次输出中同一编辑快照的资源筛选结果
type editorResourceSelection struct {
owner *Editor
fonts, images []editorResource
spaces []ColorSpace
files map[string]bool
}
// usedResources 沿图层、对象及资源依赖筛选新增资源和共享文件,不修改资源池 // usedResources 沿图层、对象及资源依赖筛选新增资源和共享文件,不修改资源池
// 入参: ctx 取消上下文 // 入参: ctx 取消上下文
// 返回: []editorResource 新增字体, []editorResource 新增图片, []ColorSpace 新增颜色空间, map[string]bool 独立资源文件集合, error 取消错误 // 返回: []editorResource 新增字体, []editorResource 新增图片, []ColorSpace 新增颜色空间, map[string]bool 独立资源文件集合, error 取消错误
@@ -798,6 +806,10 @@ func (e *Editor) usedResources(ctx context.Context) (fonts, images []editorResou
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
if e.output != nil && e.output.resources != nil && e.output.resources.owner == e {
cached := e.output.resources
return slices.Clone(cached.fonts), slices.Clone(cached.images), slices.Clone(cached.spaces), maps.Clone(cached.files), nil
}
used := make(map[string]bool) used := make(map[string]bool)
promoted := make(map[string]bool) promoted := make(map[string]bool)
files := make(map[string]bool) files := make(map[string]bool)
@@ -945,6 +957,9 @@ func (e *Editor) usedResources(ctx context.Context) (fonts, images []editorResou
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, nil, nil, nil, err return nil, nil, nil, nil, err
} }
if e.output != nil {
e.output.resources = &editorResourceSelection{owner: e, fonts: slices.Clone(fonts), images: slices.Clone(images), spaces: slices.Clone(spaces), files: maps.Clone(files)}
}
return fonts, images, spaces, files, nil return fonts, images, spaces, files, nil
} }