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
+404 -51

No files matched your search

+38 -1
View File
@@ -4828,6 +4828,9 @@ async function changeDocument(name, item, ...args) {
openSeq = advanceDocumentSequence();
if (changingFont) {
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) {
await refreshEditorPage(doc, item ? item.index : args[0], openSeq, clearSelection);
} else {
@@ -4960,7 +4963,7 @@ async function refreshFontBinding(doc, openSeq) {
async function refreshEditorPage(doc, index, openSeq, clearSelection = false) {
state.doc = doc;
resetSearch();
const search = refreshSearchPages([index], openSeq);
state.documentSelection = null;
pageView.resetPageLoading();
try {
@@ -4997,6 +5000,7 @@ async function refreshEditorPage(doc, index, openSeq, clearSelection = false) {
renderMeta(true);
updateControls();
loadDocumentDetails(openSeq);
await search;
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);
}
@@ -10771,6 +10775,39 @@ function resetSearch(clearInput = true) {
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() {
if (!state.doc || document.body.hasAttribute("data-busy")) {
return;
+21 -1
View File
@@ -370,6 +370,7 @@ type editorInfo struct {
CanRedo bool `json:"canRedo"`
PageCapabilities []uint `json:"pageCapabilities"`
EditWarnings []string `json:"editWarnings,omitempty"`
Changes ofdgo.EditorChanges `json:"changes"`
}
// editorPageInfo 页面操作结果与目标页面
@@ -3203,7 +3204,7 @@ func (e *businessEngine) encodeResult(result apiResult) string {
// 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 {
info.EditWarnings = append(info.EditWarnings, "原文件声明不允许编辑")
}
@@ -6741,6 +6742,25 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) (
session.Renderer.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 {
session.input, e.currentSession.input = e.currentSession.input, nil
}
+1
View File
@@ -70,6 +70,7 @@ type outputOptimization struct {
protected bool
completed int
deflater *flate.Writer
resources *editorResourceSelection
}
// outputCompressionBuffer 限制候选ZIP编码,不能缩小时终止本次压缩
+5 -7
View File
@@ -77,6 +77,9 @@ type Editor struct {
saveTarget Output
snapshotArchive *editorSnapshotArchive
validatedPaths *renderCache[string, bool]
changes EditorChanges
transactionChanges *EditorChanges
objectIndex *editorObjectIndex
richIndex *richTextIndex
}
@@ -892,13 +895,8 @@ func (e *Editor) findObject(page int, id string) (*Layer, int, error) {
if err != nil {
return nil, 0, err
}
for i := range content.Content.Layer {
layer := &content.Content.Layer[i]
for index, object := range layer.Objects {
if editorObjectID(object) == id && id != "" {
return layer, index, nil
}
}
if position, ok := e.objectPositions(content)[id]; ok {
return &content.Content.Layer[position.layer], position.index, nil
}
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
}
// 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不含嵌套页块的对象
type ObjectPosition struct {
Layer string
@@ -709,7 +739,7 @@ func (e *Editor) replaceLayers(page int, layers []Layer) {
func (e *Editor) replacePreparedLayers(page int, after []Layer) {
if e.historyLimit == 0 {
e.pages[page].Content.Layer = after
e.recordChange()
e.recordChange(page)
return
}
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
}
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.redo = func(e *Editor) { apply(e, after) }
}
@@ -835,6 +865,15 @@ func (e *Editor) updateObjectOrigins(page int, objects []GraphicObject, preserve
return nil
}
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 {
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)
if change := e.recordChange(); change != nil {
if change := e.recordChange(page); change != nil {
change.undo = func(e *Editor) { apply(e, before, beforeOrigins) }
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 {
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 {
if _, exists := selected[id]; exists {
if selected[id] {
return nil, nil, fmt.Errorf("duplicate object %q", id)
}
selected[id] = i
}
for i, layer := range content.Content.Layer {
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 {
selected[id] = true
position, ok := positions[id]
if !ok {
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
}
+188
View File
@@ -19,6 +19,7 @@ import (
"encoding/xml"
"fmt"
"reflect"
"slices"
"strconv"
"sync"
)
@@ -241,6 +242,22 @@ func (e *Editor) copiedLayerStyle(object GraphicObject) string {
// 入参: value 原值
// 返回: 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)
if !source.IsValid() {
return value
@@ -336,3 +353,174 @@ func editorCloneFields(kind reflect.Type) []int {
actual, _ := editorClonePlans.LoadOrStore(kind, fields)
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
}
+18 -8
View File
@@ -34,8 +34,9 @@ type editorFontUsage struct {
unsafe bool
}
// editorFontSubset 保存单个新增字体最近一次裁剪结果,不保留历史字形集合
// editorFontSubset 保存单个新增字体及其快照最近一次裁剪结果,不保留历史字形集合
type editorFontSubset struct {
source [32]byte
glyphs []uint16
data []byte
mapped bool
@@ -317,13 +318,13 @@ func (e *Editor) subsetSnapshotFonts(ctx context.Context, progress editorProgres
if e.source == nil || e.backends.FontResources == nil {
return nil, ctx.Err()
}
candidates := make(map[string]bool)
for _, resource := range e.resources {
candidates := make(map[string]int)
for i, resource := range e.resources {
if err := ctx.Err(); err != nil {
return nil, err
}
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 {
@@ -399,15 +400,24 @@ func (e *Editor) subsetSnapshotFonts(ctx context.Context, progress editorProgres
if font.unsafe {
continue
}
subset, err := e.backends.FontResources.SubsetFont(data, slices.Sorted(maps.Keys(glyphs)), preserve)
ids := slices.Sorted(maps.Keys(glyphs))
checksum := sha256.Sum256(data)
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) != 0 && len(subset) < len(data) {
result[name] = subset
if len(subset) >= len(data) {
subset = nil
}
resource.subset = &editorFontSubset{source: checksum, glyphs: ids, data: subset, mapped: preserve}
}
if len(resource.subset.data) != 0 {
result[name] = resource.subset.data
}
}
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
}
resource := &e.resources[i]
if resource.font == nil {
if resource.font == nil || e.source != nil && e.source.reader.fileNames[resource.name] {
continue
}
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)
if change := e.recordChange(); change != nil {
if change := e.recordChange(page); change != nil {
change.undo = func(e *Editor) { apply(e, before, previous) }
change.redo = func(e *Editor) { apply(e, after, origins) }
}
+67 -11
View File
@@ -25,6 +25,39 @@ type editorChange struct {
redo func(*Editor)
before 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 将多项修改作为一次原子操作提交,失败时保留原文档及历史
@@ -37,6 +70,8 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
before.Info = cloneEditorData(e.Info)
next := e.transactionSnapshot()
next.history, next.historyIndex, next.historyLimit = nil, 0, 0
pending := &EditorChanges{}
next.transactionChanges = pending
*e = next
committed := false
defer func() {
@@ -48,6 +83,11 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
return err
}
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
if !sameBackend(e.backends.FontResources, before.backends.FontResources) {
e.clearFontResourceCache()
@@ -62,7 +102,7 @@ func (e *Editor) Transaction(edit func(*Editor) error) error {
}
if changed {
e.serial, e.revision = before.serial, before.revision
e.recordTransaction(before)
e.recordTransaction(before, changes)
}
committed = true
return nil
@@ -108,6 +148,7 @@ func (e *Editor) Undo() bool {
e.historyIndex--
change := e.history[e.historyIndex]
change.undo(e)
e.changes, e.objectIndex = change.changes, nil
e.revision = change.before
return true
}
@@ -120,6 +161,7 @@ func (e *Editor) Redo() bool {
}
change := e.history[e.historyIndex]
change.redo(e)
e.changes, e.objectIndex = change.changes, nil
e.historyIndex++
e.revision = change.after
return true
@@ -132,15 +174,12 @@ func (e *Editor) Revision() uint64 {
return e.revision
}
// transactionSnapshot 复制可变容器,保留只读XML、图元及二进制资源的共享
// transactionSnapshot 复制顶层容器,共享按替换方式更新的页面图层、图元和二进制资源
// 返回: Editor 独立事务状态
func (e *Editor) transactionSnapshot() Editor {
next := *e
next.Info = cloneEditorData(e.Info)
next.pages = make([]PageContent, len(e.pages))
for i, page := range e.pages {
next.pages[i] = copyEditorPage(page)
}
next.pages = slices.Clone(e.pages)
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.origins = maps.Clone(e.origins)
@@ -213,9 +252,13 @@ func (e *Editor) retainDocumentResources(source *Editor) {
}
// recordTransaction 记录事务快照,在各自资源视图中保留后续注册的资源
// 入参: before 修改前的文档状态
func (e *Editor) recordTransaction(before Editor) {
if change := e.recordChange(); change != nil {
// 入参: before 修改前的文档状态, changes 可选的事务变化范围
func (e *Editor) recordTransaction(before Editor, changes ...EditorChanges) {
scope := EditorChanges{All: true}
if len(changes) != 0 {
scope = changes[0]
}
if change := e.recordChangeScope(scope); change != nil {
after := e.transactionSnapshot()
before.history, after.history = nil, nil
change.undo = func(e *Editor) {
@@ -230,10 +273,23 @@ func (e *Editor) recordTransaction(before Editor) {
}
// recordChange 分配修订标识并为已完成的有效修改预留记录
// 入参: pages 受影响页码,省略表示全局变化
// 返回: *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++
change := editorChange{before: e.revision, after: e.serial}
change := editorChange{before: e.revision, after: e.serial, changes: scope}
e.revision = e.serial
if e.historyLimit == 0 {
return nil
+15
View File
@@ -791,6 +791,14 @@ func (e *Editor) writePartsWithPages(ctx context.Context, write func(string, []b
return nil
}
// editorResourceSelection 保存单次输出中同一编辑快照的资源筛选结果
type editorResourceSelection struct {
owner *Editor
fonts, images []editorResource
spaces []ColorSpace
files map[string]bool
}
// usedResources 沿图层、对象及资源依赖筛选新增资源和共享文件,不修改资源池
// 入参: ctx 取消上下文
// 返回: []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 {
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)
promoted := 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 {
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
}