mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
413d41c31d
commit
f1864952ad
10 files changed
+404
-51
No files matched your search
+38
-1
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ type outputOptimization struct {
|
||||
protected bool
|
||||
completed int
|
||||
deflater *flate.Writer
|
||||
resources *editorResourceSelection
|
||||
}
|
||||
|
||||
// outputCompressionBuffer 限制候选ZIP编码,不能缩小时终止本次压缩
|
||||
|
||||
+5
-7
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user