@stll/folio-core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/headless.d.ts +52 -12
- package/dist/ai-edits/headless.js +181 -55
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/controller/collaborationModules.d.ts +7 -0
- package/dist/controller/collaborationModules.js +15 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/headerFooterEditorManager.d.ts +41 -0
- package/dist/controller/headerFooterEditorManager.js +182 -0
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/layoutPipeline.js +2 -1
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +7 -1
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- package/dist/docx/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- package/dist/docx/index.d.ts +2 -1
- package/dist/docx/index.js +2 -1
- package/dist/docx/metadataPrivacy.d.ts +40 -0
- package/dist/docx/metadataPrivacy.js +131 -0
- package/dist/docx/parser.js +4 -1
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +6 -8
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/layout-engine/measure/cache.js +2 -7
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- package/dist/layout-engine/measure/measureParagraph.js +32 -36
- package/dist/layout-engine/measure/tableCellFloating.js +39 -33
- package/dist/layout-engine/types.d.ts +5 -3
- package/dist/managers/FindReplaceManager.d.ts +8 -0
- package/dist/managers/FindReplaceManager.js +18 -0
- package/dist/managers/TableSelectionManager.d.ts +6 -0
- package/dist/managers/TableSelectionManager.js +14 -0
- package/dist/prosemirror/attrs/index.js +1 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
- package/dist/prosemirror/conversion/toProseDoc.js +40 -68
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
- package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
- package/dist/prosemirror/findReplaceSelection.js +57 -13
- package/dist/prosemirror/schema/nodes.d.ts +2 -1
- package/dist/redline.d.ts +26 -11
- package/dist/redline.js +95 -62
- package/dist/render-dom/BodySelectionOverlay.d.ts +31 -0
- package/dist/render-dom/BodySelectionOverlay.js +76 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.d.ts +28 -0
- package/dist/render-dom/HeaderFooterSelectionOverlay.js +121 -0
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +30 -0
- package/dist/render-dom/RemoteSelectionOverlay.js +67 -0
- package/dist/render-dom/RenderedDomContext.d.ts +40 -0
- package/dist/render-dom/RenderedDomContext.js +126 -0
- package/dist/render-dom/resolveSidebarItemPositions.d.ts +33 -0
- package/dist/render-dom/resolveSidebarItemPositions.js +45 -0
- package/dist/server.d.ts +5 -4
- package/dist/server.js +5 -4
- package/dist/utils/findReplace.d.ts +32 -0
- package/dist/utils/findReplace.js +118 -0
- package/dist/utils/headerFooter.js +24 -1
- package/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +1 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { clearHeaderFooterVerbatimXml } from "../docx/headerFooterVerbatim.js";
|
|
2
|
+
import { proseDocToBlocks } from "../prosemirror/conversion/fromProseDoc.js";
|
|
3
|
+
import { ExtensionManager } from "../prosemirror/extensions/ExtensionManager.js";
|
|
4
|
+
import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/AutoBidiDetectionExtension.js";
|
|
5
|
+
import { createDocumentStylesPlugin } from "../prosemirror/plugins/documentStyles.js";
|
|
6
|
+
import { ensureParaIdsInState } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
|
|
7
|
+
import { createStarterKit } from "../prosemirror/extensions/StarterKit.js";
|
|
8
|
+
import { schema } from "../prosemirror/schema/index.js";
|
|
9
|
+
import { headerFooterToProseDoc } from "../prosemirror/conversion/toProseDoc.js";
|
|
10
|
+
import { EditorState } from "prosemirror-state";
|
|
11
|
+
import { EditorView } from "prosemirror-view";
|
|
12
|
+
//#region src/controller/headerFooterEditorManager.ts
|
|
13
|
+
/**
|
|
14
|
+
* Framework-neutral lifecycle owner for persistent header/footer EditorViews.
|
|
15
|
+
*
|
|
16
|
+
* Each distinct relationship id owns one off-screen ProseMirror view. Painted
|
|
17
|
+
* header/footer instances that share that relationship therefore share live
|
|
18
|
+
* editor state, and adapters only decide when to synchronize or destroy the
|
|
19
|
+
* manager.
|
|
20
|
+
*/
|
|
21
|
+
const buildInitialState = (headerFooter, styles, theme, manager) => {
|
|
22
|
+
const proseDocOptions = {};
|
|
23
|
+
if (styles) proseDocOptions.styles = styles;
|
|
24
|
+
if (theme !== void 0) proseDocOptions.theme = theme;
|
|
25
|
+
const document = headerFooterToProseDoc(headerFooter.content, proseDocOptions);
|
|
26
|
+
return ensureBaseDirectionInState(ensureParaIdsInState(EditorState.create({
|
|
27
|
+
doc: document,
|
|
28
|
+
schema,
|
|
29
|
+
plugins: [...manager.getPlugins(), createDocumentStylesPlugin(styles)]
|
|
30
|
+
})));
|
|
31
|
+
};
|
|
32
|
+
const enumerateHeaderFooterParts = ({ headers, footers }) => {
|
|
33
|
+
const parts = [];
|
|
34
|
+
if (headers) for (const rId of headers.keys()) parts.push({
|
|
35
|
+
kind: "header",
|
|
36
|
+
rId
|
|
37
|
+
});
|
|
38
|
+
if (footers) {
|
|
39
|
+
for (const rId of footers.keys()) if (!headers?.has(rId)) parts.push({
|
|
40
|
+
kind: "footer",
|
|
41
|
+
rId
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return parts;
|
|
45
|
+
};
|
|
46
|
+
const enumerateDocumentHeaderFooterParts = (document) => {
|
|
47
|
+
if (!document?.package) return [];
|
|
48
|
+
return enumerateHeaderFooterParts({
|
|
49
|
+
headers: document.package.headers,
|
|
50
|
+
footers: document.package.footers
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const resolveHeaderFooter = (document, { kind, rId }) => {
|
|
54
|
+
return (kind === "header" ? document?.package.headers : document?.package.footers)?.get(rId) ?? null;
|
|
55
|
+
};
|
|
56
|
+
const createHeaderFooterEditorManager = (deps) => {
|
|
57
|
+
const mounted = /* @__PURE__ */ new Map();
|
|
58
|
+
const destroyMounted = (part) => {
|
|
59
|
+
part.view.destroy();
|
|
60
|
+
part.manager.destroy();
|
|
61
|
+
part.mountNode.remove();
|
|
62
|
+
};
|
|
63
|
+
const destroy = () => {
|
|
64
|
+
for (const part of mounted.values()) destroyMounted(part);
|
|
65
|
+
mounted.clear();
|
|
66
|
+
};
|
|
67
|
+
const sync = () => {
|
|
68
|
+
const host = deps.getHost();
|
|
69
|
+
if (!host) return;
|
|
70
|
+
const document = deps.getDocument();
|
|
71
|
+
const styles = deps.getStyles();
|
|
72
|
+
const theme = deps.getTheme();
|
|
73
|
+
const wanted = new Map(enumerateDocumentHeaderFooterParts(document).map((part) => [part.rId, part]));
|
|
74
|
+
for (const [rId, part] of mounted) {
|
|
75
|
+
if (wanted.has(rId)) continue;
|
|
76
|
+
destroyMounted(part);
|
|
77
|
+
mounted.delete(rId);
|
|
78
|
+
}
|
|
79
|
+
for (const part of wanted.values()) {
|
|
80
|
+
const headerFooter = resolveHeaderFooter(document, part);
|
|
81
|
+
if (!headerFooter) continue;
|
|
82
|
+
const existing = mounted.get(part.rId);
|
|
83
|
+
if (existing) {
|
|
84
|
+
if (existing.mountNode.parentElement !== host) host.append(existing.mountNode);
|
|
85
|
+
const contentIsCurrent = existing.appliedHeaderFooter === headerFooter && existing.appliedContent === headerFooter.content;
|
|
86
|
+
const contextIsCurrent = existing.appliedStyles === styles && existing.appliedTheme === theme;
|
|
87
|
+
if (contentIsCurrent && contextIsCurrent) continue;
|
|
88
|
+
existing.view.updateState(buildInitialState(headerFooter, styles, theme, existing.manager));
|
|
89
|
+
existing.appliedHeaderFooter = headerFooter;
|
|
90
|
+
existing.appliedContent = headerFooter.content;
|
|
91
|
+
existing.appliedStyles = styles;
|
|
92
|
+
existing.appliedTheme = theme;
|
|
93
|
+
existing.dirty = false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const manager = new ExtensionManager(createStarterKit());
|
|
97
|
+
manager.buildSchema();
|
|
98
|
+
manager.initializeRuntime();
|
|
99
|
+
const mountNode = host.ownerDocument.createElement("div");
|
|
100
|
+
mountNode.dataset["hfRId"] = part.rId;
|
|
101
|
+
mountNode.dataset["hfKind"] = part.kind;
|
|
102
|
+
host.append(mountNode);
|
|
103
|
+
const view = new EditorView(mountNode, {
|
|
104
|
+
state: buildInitialState(headerFooter, styles, theme, manager),
|
|
105
|
+
dispatchTransaction(transaction) {
|
|
106
|
+
const nextState = view.state.apply(transaction);
|
|
107
|
+
view.updateState(nextState);
|
|
108
|
+
const mountedPart = mounted.get(part.rId);
|
|
109
|
+
if (mountedPart && transaction.docChanged) mountedPart.dirty = true;
|
|
110
|
+
deps.onTransaction?.({
|
|
111
|
+
docChanged: transaction.docChanged,
|
|
112
|
+
kind: part.kind,
|
|
113
|
+
rId: part.rId,
|
|
114
|
+
selectionChanged: transaction.selectionSet,
|
|
115
|
+
view
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
mounted.set(part.rId, {
|
|
120
|
+
appliedContent: headerFooter.content,
|
|
121
|
+
appliedHeaderFooter: headerFooter,
|
|
122
|
+
appliedStyles: styles,
|
|
123
|
+
appliedTheme: theme,
|
|
124
|
+
dirty: false,
|
|
125
|
+
kind: part.kind,
|
|
126
|
+
manager,
|
|
127
|
+
mountNode,
|
|
128
|
+
rId: part.rId,
|
|
129
|
+
view
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
destroy,
|
|
135
|
+
getView: (rId) => mounted.get(rId)?.view ?? null,
|
|
136
|
+
listSlots: () => [...mounted.values()].map(({ kind, rId }) => ({
|
|
137
|
+
kind,
|
|
138
|
+
rId
|
|
139
|
+
})),
|
|
140
|
+
snapshotDocument: (document) => {
|
|
141
|
+
let headers = document.package.headers;
|
|
142
|
+
let footers = document.package.footers;
|
|
143
|
+
let headersChanged = false;
|
|
144
|
+
let footersChanged = false;
|
|
145
|
+
for (const { dirty, kind, rId, view } of mounted.values()) {
|
|
146
|
+
if (!dirty) continue;
|
|
147
|
+
const source = kind === "header" ? headers : footers;
|
|
148
|
+
const existing = source?.get(rId);
|
|
149
|
+
if (!source || !existing) continue;
|
|
150
|
+
const updated = {
|
|
151
|
+
...existing,
|
|
152
|
+
content: proseDocToBlocks(view.state.doc)
|
|
153
|
+
};
|
|
154
|
+
clearHeaderFooterVerbatimXml(updated);
|
|
155
|
+
if (kind === "header") {
|
|
156
|
+
if (!headersChanged) {
|
|
157
|
+
headers = new Map(headers);
|
|
158
|
+
headersChanged = true;
|
|
159
|
+
}
|
|
160
|
+
headers?.set(rId, updated);
|
|
161
|
+
} else {
|
|
162
|
+
if (!footersChanged) {
|
|
163
|
+
footers = new Map(footers);
|
|
164
|
+
footersChanged = true;
|
|
165
|
+
}
|
|
166
|
+
footers?.set(rId, updated);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!headersChanged && !footersChanged) return document;
|
|
170
|
+
const packageWithSnapshots = { ...document.package };
|
|
171
|
+
if (headersChanged && headers) packageWithSnapshots.headers = headers;
|
|
172
|
+
if (footersChanged && footers) packageWithSnapshots.footers = footers;
|
|
173
|
+
return {
|
|
174
|
+
...document,
|
|
175
|
+
package: packageWithSnapshots
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
sync
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
//#endregion
|
|
182
|
+
export { createHeaderFooterEditorManager, enumerateDocumentHeaderFooterParts, enumerateHeaderFooterParts };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
-
import { HiddenEditorApi } from "./hiddenEditorApi.js";
|
|
3
2
|
import { HiddenEditorStateReason } from "../layout-engine/layoutInstrumentation.js";
|
|
4
3
|
import { ExtensionManager } from "../prosemirror/extensions/ExtensionManager.js";
|
|
4
|
+
import { HiddenEditorApi } from "./hiddenEditorApi.js";
|
|
5
5
|
import { EditorState, Plugin, Transaction } from "prosemirror-state";
|
|
6
6
|
import { EditorView } from "prosemirror-view";
|
|
7
7
|
import * as YProseMirror from "y-prosemirror";
|
|
@@ -78,7 +78,8 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
78
78
|
if (documentSettings?.autoHyphenation === true) flowOpts.automaticHyphenation = {
|
|
79
79
|
enabled: true,
|
|
80
80
|
...documentSettings.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: documentSettings.doNotHyphenateCaps } : {},
|
|
81
|
-
...documentSettings.consecutiveHyphenLimit !== void 0 ? { consecutiveLineLimit: documentSettings.consecutiveHyphenLimit } : {}
|
|
81
|
+
...documentSettings.consecutiveHyphenLimit !== void 0 ? { consecutiveLineLimit: documentSettings.consecutiveHyphenLimit } : {},
|
|
82
|
+
...documentSettings.hyphenationZoneTwips !== void 0 ? { hyphenationZoneTwips: documentSettings.hyphenationZoneTwips } : {}
|
|
82
83
|
};
|
|
83
84
|
let newBlocks = toFlowBlocks(state.doc, flowOpts);
|
|
84
85
|
const previewState = templatePreviewValuesKey.getState(state);
|
|
@@ -5,7 +5,7 @@ import { FolioAIEditView } from "./ai-edits/apply.js";
|
|
|
5
5
|
declare const FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION: 1;
|
|
6
6
|
declare const FOLIO_DOCUMENT_OPERATION_TYPES: readonly ["replaceInBlock", "replaceRange", "commentOnRange", "formatRange", "insertAfterBlock", "insertBeforeBlock", "replaceBlock", "deleteBlock", "commentOnBlock", "insertSignatureTable"];
|
|
7
7
|
declare const FOLIO_DOCUMENT_OPERATION_MODES: readonly ["direct", "tracked-changes"];
|
|
8
|
-
declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main"];
|
|
8
|
+
declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main", "header", "footer", "footnote", "endnote"];
|
|
9
9
|
declare const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS: readonly ["blockTextHash"];
|
|
10
10
|
declare const FOLIO_DOCUMENT_OPERATION_BATCH_MODES: readonly ["best-effort", "atomic"];
|
|
11
11
|
type FolioDocumentOperation = FolioAIEditOperation;
|
|
@@ -68,8 +68,17 @@ type FolioDocumentOperationIssue = {
|
|
|
68
68
|
recovery: FolioDocumentOperationRecovery;
|
|
69
69
|
};
|
|
70
70
|
type FolioDocumentOperationStory = "main" | {
|
|
71
|
-
type: "header"
|
|
71
|
+
type: "header";
|
|
72
72
|
relationshipId: string;
|
|
73
|
+
} | {
|
|
74
|
+
type: "footer";
|
|
75
|
+
relationshipId: string;
|
|
76
|
+
} | {
|
|
77
|
+
type: "footnote";
|
|
78
|
+
noteId: number;
|
|
79
|
+
} | {
|
|
80
|
+
type: "endnote";
|
|
81
|
+
noteId: number;
|
|
73
82
|
};
|
|
74
83
|
/** One typed target affected by a successfully applied document operation. */
|
|
75
84
|
type FolioDocumentOperationAffectedTarget = {
|
|
@@ -15,7 +15,13 @@ const FOLIO_DOCUMENT_OPERATION_TYPES = Object.freeze([
|
|
|
15
15
|
"insertSignatureTable"
|
|
16
16
|
]);
|
|
17
17
|
const FOLIO_DOCUMENT_OPERATION_MODES = Object.freeze(["direct", "tracked-changes"]);
|
|
18
|
-
const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
|
|
18
|
+
const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
|
|
19
|
+
"main",
|
|
20
|
+
"header",
|
|
21
|
+
"footer",
|
|
22
|
+
"footnote",
|
|
23
|
+
"endnote"
|
|
24
|
+
]);
|
|
19
25
|
const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
|
|
20
26
|
const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
|
|
21
27
|
const DIRECT_AND_TRACKED_MODES = FOLIO_DOCUMENT_OPERATION_MODES;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FolioDocumentStoryHandle } from "./ai-edits/headless.js";
|
|
2
|
+
|
|
3
|
+
//#region src/document-stories.d.ts
|
|
4
|
+
type FolioDocumentStoryPair = {
|
|
5
|
+
baseStory: FolioDocumentStoryHandle | null;
|
|
6
|
+
revisedStory: FolioDocumentStoryHandle | null;
|
|
7
|
+
};
|
|
8
|
+
declare const pairFolioDocumentStories: (baseStories: readonly FolioDocumentStoryHandle[], revisedStories: readonly FolioDocumentStoryHandle[]) => FolioDocumentStoryPair[];
|
|
9
|
+
//#endregion
|
|
10
|
+
export { FolioDocumentStoryPair, pairFolioDocumentStories };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/document-stories.ts
|
|
2
|
+
const documentStoryKey = (story) => {
|
|
3
|
+
if (story.type === "main") return story.type;
|
|
4
|
+
if (story.type === "header" || story.type === "footer") return `${story.type}:${story.relationshipId}`;
|
|
5
|
+
return `${story.type}:${String(story.noteId)}`;
|
|
6
|
+
};
|
|
7
|
+
const pairFolioDocumentStories = (baseStories, revisedStories) => {
|
|
8
|
+
const revisedByKey = new Map(revisedStories.map((story) => [documentStoryKey(story), story]));
|
|
9
|
+
const pairedKeys = /* @__PURE__ */ new Set();
|
|
10
|
+
const pairs = [];
|
|
11
|
+
for (const baseStory of baseStories) {
|
|
12
|
+
const key = documentStoryKey(baseStory);
|
|
13
|
+
const revisedStory = revisedByKey.get(key) ?? null;
|
|
14
|
+
pairs.push({
|
|
15
|
+
baseStory,
|
|
16
|
+
revisedStory
|
|
17
|
+
});
|
|
18
|
+
if (revisedStory) pairedKeys.add(key);
|
|
19
|
+
}
|
|
20
|
+
for (const revisedStory of revisedStories) if (!pairedKeys.has(documentStoryKey(revisedStory))) pairs.push({
|
|
21
|
+
baseStory: null,
|
|
22
|
+
revisedStory
|
|
23
|
+
});
|
|
24
|
+
return pairs;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { pairFolioDocumentStories };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
|
|
3
|
+
//#region src/docx/corePropertiesParser.d.ts
|
|
4
|
+
type DocumentProperties = NonNullable<document_d_exports.DocxPackage["properties"]>;
|
|
5
|
+
/** Parse recognized package metadata from `docProps/core.xml`. */
|
|
6
|
+
declare const parseCoreProperties: (xml: string | null) => DocumentProperties | undefined;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { parseCoreProperties };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { findChildByLocalName, getTextContent, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/corePropertiesParser.ts
|
|
3
|
+
const STRING_PROPERTIES = [
|
|
4
|
+
"title",
|
|
5
|
+
"subject",
|
|
6
|
+
"creator",
|
|
7
|
+
"keywords",
|
|
8
|
+
"description",
|
|
9
|
+
"lastModifiedBy"
|
|
10
|
+
];
|
|
11
|
+
const parseRevision = (value) => {
|
|
12
|
+
if (!/^\d+$/u.test(value)) return;
|
|
13
|
+
const revision = Number(value);
|
|
14
|
+
return Number.isSafeInteger(revision) ? revision : void 0;
|
|
15
|
+
};
|
|
16
|
+
const parseDate = (value) => {
|
|
17
|
+
const timestamp = Date.parse(value);
|
|
18
|
+
return Number.isNaN(timestamp) ? void 0 : new Date(timestamp);
|
|
19
|
+
};
|
|
20
|
+
/** Parse recognized package metadata from `docProps/core.xml`. */
|
|
21
|
+
const parseCoreProperties = (xml) => {
|
|
22
|
+
if (!xml) return;
|
|
23
|
+
const root = parseXmlDocument(xml);
|
|
24
|
+
if (!root) return;
|
|
25
|
+
const properties = {};
|
|
26
|
+
let recognizedPropertyCount = 0;
|
|
27
|
+
for (const property of STRING_PROPERTIES) {
|
|
28
|
+
const element = findChildByLocalName(root, property);
|
|
29
|
+
if (!element) continue;
|
|
30
|
+
properties[property] = getTextContent(element);
|
|
31
|
+
recognizedPropertyCount++;
|
|
32
|
+
}
|
|
33
|
+
const revisionElement = findChildByLocalName(root, "revision");
|
|
34
|
+
if (revisionElement) {
|
|
35
|
+
const revision = parseRevision(getTextContent(revisionElement));
|
|
36
|
+
if (revision !== void 0) {
|
|
37
|
+
properties.revision = revision;
|
|
38
|
+
recognizedPropertyCount++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const property of ["created", "modified"]) {
|
|
42
|
+
const element = findChildByLocalName(root, property);
|
|
43
|
+
if (!element) continue;
|
|
44
|
+
const date = parseDate(getTextContent(element));
|
|
45
|
+
if (date !== void 0) {
|
|
46
|
+
properties[property] = date;
|
|
47
|
+
recognizedPropertyCount++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return recognizedPropertyCount > 0 ? properties : void 0;
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { parseCoreProperties };
|
package/dist/docx/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCachedNumberingMap } from "./numberingParser.js";
|
|
2
2
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, DocxEncryptionErrorCode, isDocxEncryptionError } from "./encryption/errors.js";
|
|
3
3
|
import { DecryptDocxOptions, DecryptDocxResult, decryptDocxIfNeeded, openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
4
|
-
|
|
4
|
+
import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport, FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, RewriteDocxMetadataPrivacyResult, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./metadataPrivacy.js";
|
|
5
|
+
export { DOCX_ENCRYPTION_ERROR_CODES, type DecryptDocxOptions, type DecryptDocxResult, DocxEncryptionError, type DocxEncryptionErrorCode, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, type FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, type RewriteDocxMetadataPrivacyResult, decryptDocxIfNeeded, getCachedNumberingMap, isDocxEncryptionError, isFolioDocumentPrivacyTransform, openDocxBuffer, rewriteDocxMetadataPrivacy };
|
package/dist/docx/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCachedNumberingMap } from "./numberingParser.js";
|
|
2
2
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, isDocxEncryptionError } from "./encryption/errors.js";
|
|
3
3
|
import { decryptDocxIfNeeded, openDocxBuffer } from "./encryption/openEncryptedDocx.js";
|
|
4
|
-
|
|
4
|
+
import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./metadataPrivacy.js";
|
|
5
|
+
export { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, decryptDocxIfNeeded, getCachedNumberingMap, isDocxEncryptionError, isFolioDocumentPrivacyTransform, openDocxBuffer, rewriteDocxMetadataPrivacy };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/docx/metadataPrivacy.d.ts
|
|
2
|
+
declare const FOLIO_DOCUMENT_METADATA_PROPERTIES: readonly ["title", "subject", "creator", "keywords", "description", "lastModifiedBy", "revision", "created", "modified"];
|
|
3
|
+
type FolioDocumentMetadataProperty = (typeof FOLIO_DOCUMENT_METADATA_PROPERTIES)[number];
|
|
4
|
+
declare const FOLIO_DOCUMENT_PRIVACY_TRANSFORMS: readonly ["remove-attribution", "remove-timestamps", "remove-descriptive-metadata"];
|
|
5
|
+
type FolioDocumentPrivacyTransform = (typeof FOLIO_DOCUMENT_PRIVACY_TRANSFORMS)[number];
|
|
6
|
+
declare const isFolioDocumentPrivacyTransform: (value: unknown) => value is FolioDocumentPrivacyTransform;
|
|
7
|
+
type FolioDocumentPrivacyOptions = {
|
|
8
|
+
transforms: readonly FolioDocumentPrivacyTransform[];
|
|
9
|
+
};
|
|
10
|
+
type FolioDocumentPrivacyReport = {
|
|
11
|
+
appliedTransforms: FolioDocumentPrivacyTransform[];
|
|
12
|
+
removedMetadataProperties: FolioDocumentMetadataProperty[];
|
|
13
|
+
};
|
|
14
|
+
type RewriteDocxMetadataPrivacyResult = {
|
|
15
|
+
buffer: ArrayBuffer;
|
|
16
|
+
privacyReport: FolioDocumentPrivacyReport;
|
|
17
|
+
};
|
|
18
|
+
declare const InvalidFolioDocumentPrivacyOptionsError_base: import("better-result").TaggedErrorClass<"InvalidFolioDocumentPrivacyOptionsError", {
|
|
19
|
+
message: string;
|
|
20
|
+
receivedValue: unknown;
|
|
21
|
+
}>;
|
|
22
|
+
declare class InvalidFolioDocumentPrivacyOptionsError extends InvalidFolioDocumentPrivacyOptionsError_base {}
|
|
23
|
+
declare const FolioDocumentPrivacyArchiveError_base: import("better-result").TaggedErrorClass<"FolioDocumentPrivacyArchiveError", {
|
|
24
|
+
message: string;
|
|
25
|
+
reason: "input-too-large" | "load-failed" | "too-many-entries" | "core-properties-too-large";
|
|
26
|
+
cause?: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
declare class FolioDocumentPrivacyArchiveError extends FolioDocumentPrivacyArchiveError_base {}
|
|
29
|
+
declare const PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM: {
|
|
30
|
+
readonly "remove-attribution": readonly ["creator", "lastModifiedBy"];
|
|
31
|
+
readonly "remove-timestamps": readonly ["created", "modified"];
|
|
32
|
+
readonly "remove-descriptive-metadata": readonly ["title", "subject", "keywords", "description"];
|
|
33
|
+
};
|
|
34
|
+
declare const resolveFolioDocumentPrivacyTransforms: (transforms: unknown) => FolioDocumentPrivacyTransform[];
|
|
35
|
+
/** Rewrite selected package metadata fields without changing other package parts. */
|
|
36
|
+
declare const rewriteDocxMetadataPrivacy: (buffer: ArrayBuffer, {
|
|
37
|
+
transforms
|
|
38
|
+
}: FolioDocumentPrivacyOptions) => Promise<RewriteDocxMetadataPrivacyResult>;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentMetadataProperty, FolioDocumentPrivacyArchiveError, FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport, FolioDocumentPrivacyTransform, InvalidFolioDocumentPrivacyOptionsError, PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM, RewriteDocxMetadataPrivacyResult, isFolioDocumentPrivacyTransform, resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { elementToXml, getLocalName, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
+
import { TaggedError } from "better-result";
|
|
3
|
+
import JSZip from "jszip";
|
|
4
|
+
//#region src/docx/metadataPrivacy.ts
|
|
5
|
+
const FOLIO_DOCUMENT_METADATA_PROPERTIES = Object.freeze([
|
|
6
|
+
"title",
|
|
7
|
+
"subject",
|
|
8
|
+
"creator",
|
|
9
|
+
"keywords",
|
|
10
|
+
"description",
|
|
11
|
+
"lastModifiedBy",
|
|
12
|
+
"revision",
|
|
13
|
+
"created",
|
|
14
|
+
"modified"
|
|
15
|
+
]);
|
|
16
|
+
const isFolioDocumentMetadataProperty = (value) => FOLIO_DOCUMENT_METADATA_PROPERTIES.some((property) => property === value);
|
|
17
|
+
const FOLIO_DOCUMENT_PRIVACY_TRANSFORMS = Object.freeze([
|
|
18
|
+
"remove-attribution",
|
|
19
|
+
"remove-timestamps",
|
|
20
|
+
"remove-descriptive-metadata"
|
|
21
|
+
]);
|
|
22
|
+
const isFolioDocumentPrivacyTransform = (value) => FOLIO_DOCUMENT_PRIVACY_TRANSFORMS.some((transform) => transform === value);
|
|
23
|
+
var InvalidFolioDocumentPrivacyOptionsError = class extends TaggedError("InvalidFolioDocumentPrivacyOptionsError")() {};
|
|
24
|
+
var FolioDocumentPrivacyArchiveError = class extends TaggedError("FolioDocumentPrivacyArchiveError")() {};
|
|
25
|
+
const PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM = {
|
|
26
|
+
"remove-attribution": ["creator", "lastModifiedBy"],
|
|
27
|
+
"remove-timestamps": ["created", "modified"],
|
|
28
|
+
"remove-descriptive-metadata": [
|
|
29
|
+
"title",
|
|
30
|
+
"subject",
|
|
31
|
+
"keywords",
|
|
32
|
+
"description"
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
const resolveFolioDocumentPrivacyTransforms = (transforms) => {
|
|
36
|
+
if (!Array.isArray(transforms) || transforms.some((transform) => !isFolioDocumentPrivacyTransform(transform))) throw new InvalidFolioDocumentPrivacyOptionsError({
|
|
37
|
+
message: "Document privacy received an unrecognized transform",
|
|
38
|
+
receivedValue: transforms
|
|
39
|
+
});
|
|
40
|
+
const requested = new Set(transforms);
|
|
41
|
+
return FOLIO_DOCUMENT_PRIVACY_TRANSFORMS.filter((transform) => requested.has(transform));
|
|
42
|
+
};
|
|
43
|
+
const XML_DECLARATION_PATTERN = /^\s*<\?xml[^?]*\?>/u;
|
|
44
|
+
const MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
45
|
+
const MAX_ARCHIVE_ENTRIES = 5e3;
|
|
46
|
+
const MAX_CORE_PROPERTIES_BYTES = 1024 * 1024;
|
|
47
|
+
const loadPrivacyArchive = async (buffer) => {
|
|
48
|
+
if (buffer.byteLength > MAX_INPUT_BYTES) throw new FolioDocumentPrivacyArchiveError({
|
|
49
|
+
message: "Document privacy input exceeded the compressed-size limit",
|
|
50
|
+
reason: "input-too-large"
|
|
51
|
+
});
|
|
52
|
+
let zip;
|
|
53
|
+
try {
|
|
54
|
+
zip = await JSZip.loadAsync(buffer);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw new FolioDocumentPrivacyArchiveError({
|
|
57
|
+
message: "Document privacy input is not a readable package",
|
|
58
|
+
reason: "load-failed",
|
|
59
|
+
cause
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (Object.values(zip.files).length > MAX_ARCHIVE_ENTRIES) throw new FolioDocumentPrivacyArchiveError({
|
|
63
|
+
message: "Document privacy input exceeded the package-entry limit",
|
|
64
|
+
reason: "too-many-entries"
|
|
65
|
+
});
|
|
66
|
+
const coreProperties = zip.file("docProps/core.xml");
|
|
67
|
+
if (!coreProperties) return zip;
|
|
68
|
+
const data = "_data" in coreProperties ? coreProperties._data : void 0;
|
|
69
|
+
const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
|
|
70
|
+
if (typeof declaredBytes !== "number" || declaredBytes > MAX_CORE_PROPERTIES_BYTES) throw new FolioDocumentPrivacyArchiveError({
|
|
71
|
+
message: "Document privacy core properties exceeded the part-size limit",
|
|
72
|
+
reason: "core-properties-too-large"
|
|
73
|
+
});
|
|
74
|
+
return zip;
|
|
75
|
+
};
|
|
76
|
+
const rewriteCorePropertiesPrivacy = (xml, transforms) => {
|
|
77
|
+
const root = parseXmlDocument(xml);
|
|
78
|
+
if (!root?.elements) return {
|
|
79
|
+
xml,
|
|
80
|
+
removedMetadataProperties: []
|
|
81
|
+
};
|
|
82
|
+
const propertiesToRemove = /* @__PURE__ */ new Set();
|
|
83
|
+
for (const transform of transforms) for (const property of PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM[transform]) propertiesToRemove.add(property);
|
|
84
|
+
const removedPropertySet = /* @__PURE__ */ new Set();
|
|
85
|
+
root.elements = root.elements.filter((element) => {
|
|
86
|
+
if (element.type !== "element") return true;
|
|
87
|
+
const property = getLocalName(element.name ?? "");
|
|
88
|
+
if (!isFolioDocumentMetadataProperty(property)) return true;
|
|
89
|
+
if (!propertiesToRemove.has(property)) return true;
|
|
90
|
+
removedPropertySet.add(property);
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
xml: `${xml.match(XML_DECLARATION_PATTERN)?.at(0) ?? ""}${elementToXml(root)}`,
|
|
95
|
+
removedMetadataProperties: FOLIO_DOCUMENT_METADATA_PROPERTIES.filter((property) => removedPropertySet.has(property))
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/** Rewrite selected package metadata fields without changing other package parts. */
|
|
99
|
+
const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
|
|
100
|
+
const appliedTransforms = resolveFolioDocumentPrivacyTransforms(transforms);
|
|
101
|
+
const zip = await loadPrivacyArchive(buffer);
|
|
102
|
+
const coreProperties = zip.file("docProps/core.xml");
|
|
103
|
+
if (!coreProperties) return {
|
|
104
|
+
buffer,
|
|
105
|
+
privacyReport: {
|
|
106
|
+
appliedTransforms,
|
|
107
|
+
removedMetadataProperties: []
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const rewritten = rewriteCorePropertiesPrivacy(await coreProperties.async("text"), appliedTransforms);
|
|
111
|
+
if (rewritten.removedMetadataProperties.length === 0) return {
|
|
112
|
+
buffer,
|
|
113
|
+
privacyReport: {
|
|
114
|
+
appliedTransforms,
|
|
115
|
+
removedMetadataProperties: []
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
zip.file("docProps/core.xml", rewritten.xml);
|
|
119
|
+
return {
|
|
120
|
+
buffer: await zip.generateAsync({
|
|
121
|
+
type: "arraybuffer",
|
|
122
|
+
compression: "DEFLATE"
|
|
123
|
+
}),
|
|
124
|
+
privacyReport: {
|
|
125
|
+
appliedTransforms,
|
|
126
|
+
removedMetadataProperties: rewritten.removedMetadataProperties
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, PRIVATE_METADATA_PROPERTIES_BY_TRANSFORM, isFolioDocumentPrivacyTransform, resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy };
|
package/dist/docx/parser.js
CHANGED
|
@@ -6,6 +6,7 @@ import { applyThemeFontLang, parseTheme } from "./themeParser.js";
|
|
|
6
6
|
import { parseComments } from "./commentParser.js";
|
|
7
7
|
import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
|
|
8
8
|
import { detectDocxConformanceClass } from "./conformance.js";
|
|
9
|
+
import { parseCoreProperties } from "./corePropertiesParser.js";
|
|
9
10
|
import { parseNumbering } from "./numberingParser.js";
|
|
10
11
|
import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
|
|
11
12
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
@@ -168,6 +169,7 @@ async function parseDocx(input, options = {}) {
|
|
|
168
169
|
onProgress("Loaded fonts", 95);
|
|
169
170
|
} else onProgress("Skipping font loading", 95);
|
|
170
171
|
onProgress("Assembling document...", 95);
|
|
172
|
+
const properties = timeStage("coreProperties", () => parseCoreProperties(raw.corePropsXml));
|
|
171
173
|
const document = {
|
|
172
174
|
package: {
|
|
173
175
|
conformanceClass: detectDocxConformanceClass(raw.documentXml),
|
|
@@ -182,7 +184,8 @@ async function parseDocx(input, options = {}) {
|
|
|
182
184
|
...footnotes !== void 0 ? { footnotes } : {},
|
|
183
185
|
...endnotes !== void 0 ? { endnotes } : {},
|
|
184
186
|
relationships: rels,
|
|
185
|
-
media
|
|
187
|
+
media,
|
|
188
|
+
...properties !== void 0 ? { properties } : {}
|
|
186
189
|
},
|
|
187
190
|
originalBuffer: raw.originalBuffer,
|
|
188
191
|
...templateVariables !== void 0 ? { templateVariables } : {},
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -124,10 +124,11 @@ declare function hasUnmaterializedHeaderFooter(doc: document_d_exports.Document)
|
|
|
124
124
|
*/
|
|
125
125
|
declare function hasModelDrivenPictureWatermark(doc: document_d_exports.Document): boolean;
|
|
126
126
|
declare function collectHeaderFooterUpdates(doc: document_d_exports.Document): Map<string, string>;
|
|
127
|
-
/**
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
/** Update existing core-property values without synthesizing absent metadata. */
|
|
128
|
+
declare function updateCoreProperties(corePropsXml: string, {
|
|
129
|
+
updateModifiedDate,
|
|
130
|
+
modifiedBy
|
|
131
|
+
}: {
|
|
131
132
|
updateModifiedDate?: boolean;
|
|
132
133
|
modifiedBy?: string;
|
|
133
134
|
}): string;
|
package/dist/docx/rezip.js
CHANGED
|
@@ -1022,18 +1022,16 @@ function findNotePartEntry(zip, conventionalLowerPath) {
|
|
|
1022
1022
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.toLowerCase() === conventionalLowerPath) return file;
|
|
1023
1023
|
return null;
|
|
1024
1024
|
}
|
|
1025
|
-
/**
|
|
1026
|
-
|
|
1027
|
-
*/
|
|
1028
|
-
function updateCoreProperties(corePropsXml, options) {
|
|
1025
|
+
/** Update existing core-property values without synthesizing absent metadata. */
|
|
1026
|
+
function updateCoreProperties(corePropsXml, { updateModifiedDate, modifiedBy }) {
|
|
1029
1027
|
let result = corePropsXml;
|
|
1030
|
-
if (
|
|
1028
|
+
if (updateModifiedDate) {
|
|
1031
1029
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1032
1030
|
if (result.includes("<dcterms:modified")) result = result.replace(/<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u, `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>`);
|
|
1033
|
-
else result = result.replace("</cp:coreProperties>", `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified></cp:coreProperties>`);
|
|
1034
1031
|
}
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1032
|
+
if (modifiedBy) {
|
|
1033
|
+
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${escapeXml(modifiedBy)}</cp:lastModifiedBy>`);
|
|
1034
|
+
}
|
|
1037
1035
|
return result;
|
|
1038
1036
|
}
|
|
1039
1037
|
/**
|
|
@@ -445,6 +445,7 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
|
|
|
445
445
|
if (attrs.cropBottom != null) run.cropBottom = attrs.cropBottom;
|
|
446
446
|
if (attrs.cropLeft != null) run.cropLeft = attrs.cropLeft;
|
|
447
447
|
if (attrs.position !== void 0) run.position = attrs.position;
|
|
448
|
+
if (attrs.layoutInCell !== void 0) run.layoutInCell = attrs.layoutInCell;
|
|
448
449
|
if (trackedChange?.isInsertion) run.isInsertion = true;
|
|
449
450
|
if (trackedChange?.isDeletion) run.isDeletion = true;
|
|
450
451
|
if (trackedChange?.changeAuthor !== void 0) run.changeAuthor = trackedChange.changeAuthor;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
|
|
2
|
+
import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
|
|
2
3
|
//#region src/layout-engine/measure/cache.ts
|
|
3
4
|
/**
|
|
4
5
|
* Current max size for text width cache
|
|
@@ -168,13 +169,7 @@ function hashParagraphBlock(block) {
|
|
|
168
169
|
if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
|
|
169
170
|
if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
|
|
170
171
|
if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
|
|
171
|
-
|
|
172
|
-
if (attrs.overflowPunctuation !== void 0) parts.push(`overflow-punct:${attrs.overflowPunctuation}`);
|
|
173
|
-
if (attrs.suppressAutoHyphens !== void 0) parts.push(`suppress-auto-hyphens:${attrs.suppressAutoHyphens}`);
|
|
174
|
-
const automaticHyphenation = attrs.automaticHyphenation;
|
|
175
|
-
if (automaticHyphenation) parts.push(`auto-hyphens:${automaticHyphenation.doNotHyphenateCaps}|${automaticHyphenation.consecutiveLineLimit}`);
|
|
176
|
-
const lineBreakRules = attrs.lineBreakRules;
|
|
177
|
-
if (lineBreakRules) parts.push(`line-break-rules:${lineBreakRules.noLineBreaksBefore?.language}|${lineBreakRules.noLineBreaksBefore?.characters}|${lineBreakRules.noLineBreaksAfter?.language}|${lineBreakRules.noLineBreaksAfter?.characters}|${lineBreakRules.useLegacyEthiopicAmharicRules}`);
|
|
172
|
+
parts.push(...lineBreakPolicyCacheParts(attrs));
|
|
178
173
|
const borders = attrs.borders;
|
|
179
174
|
if (borders) {
|
|
180
175
|
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|