@stll/folio-core 0.47.7 → 0.49.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/apply.js +565 -121
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/fontReadiness.js +3 -3
- package/dist/controller/hyphenationReadiness.d.ts +19 -0
- package/dist/controller/hyphenationReadiness.js +47 -0
- package/dist/controller/layoutPipeline.d.ts +3 -0
- package/dist/controller/layoutPipeline.js +70 -18
- package/dist/display-list/build/buildDisplayList.js +39 -12
- package/dist/display-list/build/pageFurniture.d.ts +7 -1
- package/dist/display-list/build/pageFurniture.js +21 -8
- package/dist/display-list/build/paragraphPrimitives.js +68 -11
- package/dist/display-list/build/tablePrimitives.js +10 -6
- package/dist/display-list/build/textBoxPrimitives.js +1 -1
- package/dist/display-list/build/unsupported.d.ts +2 -2
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
- package/dist/docx/attributeRemainder.js +10 -2
- package/dist/docx/blockContentParser.d.ts +2 -0
- package/dist/docx/blockContentParser.js +79 -64
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- package/dist/docx/commentParser.d.ts +2 -1
- package/dist/docx/commentParser.js +57 -45
- package/dist/docx/containerChildren.d.ts +21 -8
- package/dist/docx/containerChildren.js +39 -9
- package/dist/docx/documentParser.d.ts +5 -1
- package/dist/docx/documentParser.js +8 -5
- package/dist/docx/documentSectionFacts.d.ts +22 -0
- package/dist/docx/documentSectionFacts.js +43 -0
- package/dist/docx/drawingGroupChildren.d.ts +60 -0
- package/dist/docx/drawingGroupChildren.js +176 -0
- package/dist/docx/drawingUtils.js +3 -0
- package/dist/docx/fieldParser.d.ts +8 -1
- package/dist/docx/fieldParser.js +47 -3
- package/dist/docx/fontTableParser.js +34 -27
- package/dist/docx/footnoteParser.d.ts +3 -2
- package/dist/docx/footnoteParser.js +15 -12
- package/dist/docx/groupDrawingParser.d.ts +9 -2
- package/dist/docx/groupDrawingParser.js +125 -37
- package/dist/docx/headerFooterParser.d.ts +7 -2
- package/dist/docx/headerFooterParser.js +15 -5
- package/dist/docx/hyperlinkParser.d.ts +48 -36
- package/dist/docx/hyperlinkParser.js +97 -82
- package/dist/docx/numberingParser.js +108 -84
- package/dist/docx/packageParts.d.ts +45 -1
- package/dist/docx/packageParts.js +93 -2
- package/dist/docx/paraIdAttribute.d.ts +3 -2
- package/dist/docx/paragraphParser.d.ts +5 -1
- package/dist/docx/paragraphParser.js +468 -365
- package/dist/docx/paragraphProperties.js +123 -109
- package/dist/docx/paragraphPropertySource.d.ts +6 -1
- package/dist/docx/paragraphPropertySource.js +9 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
- package/dist/docx/parser.d.ts +7 -1
- package/dist/docx/parser.js +33 -16
- package/dist/docx/previewBudget.d.ts +55 -33
- package/dist/docx/previewBudget.js +59 -65
- package/dist/docx/rezip.js +41 -58
- package/dist/docx/runParser.d.ts +4 -1
- package/dist/docx/runParser.js +182 -184
- package/dist/docx/sdtProperties.js +87 -84
- package/dist/docx/sectionParser.js +169 -160
- package/dist/docx/selectiveSaveFlags.d.ts +10 -3
- package/dist/docx/selectiveSaveFlags.js +1 -1
- package/dist/docx/selectiveXmlPatch.d.ts +56 -34
- package/dist/docx/selectiveXmlPatch.js +222 -115
- package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
- package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
- package/dist/docx/serializer/paragraphSerializer.js +47 -42
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/serializer/settingsSerializer.js +19 -1
- package/dist/docx/settingsParser.js +8 -0
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/streamingXmlParser.d.ts +6 -2
- package/dist/docx/streamingXmlParser.js +12 -7
- package/dist/docx/styleParser.js +34 -8
- package/dist/docx/tableParser.d.ts +21 -12
- package/dist/docx/tableParser.js +518 -441
- package/dist/docx/textBoxParser.d.ts +18 -5
- package/dist/docx/textBoxParser.js +12 -6
- package/dist/docx/verbatimCapture.d.ts +10 -8
- package/dist/docx/verbatimCapture.js +45 -2
- package/dist/docx/vmlImageParser.d.ts +15 -13
- package/dist/docx/vmlImageParser.js +75 -60
- package/dist/docx/vmlPreview.d.ts +3 -3
- package/dist/docx/vmlPreview.js +3 -4
- package/dist/docx/xmlNamespaceContext.d.ts +9 -0
- package/dist/docx/xmlNamespaceContext.js +54 -0
- package/dist/docx/xmlParser.d.ts +6 -3
- package/dist/docx/xmlParser.js +11 -41
- package/dist/docx/xmlResourceLimits.d.ts +2 -1
- package/dist/docx/xmlResourceLimits.js +4 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/headless-layout.d.ts +11 -2
- package/dist/headless-layout.js +153 -107
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/internal/paragraphFormattingSerialization.js +3 -3
- package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
- package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
- package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
- package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
- package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
- package/dist/layout-bridge/engine/hitTest.js +5 -3
- package/dist/layout-bridge/engine/selectionRects.js +5 -3
- package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
- package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +102 -14
- package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
- package/dist/layout-engine/justificationCompatibility.js +9 -5
- package/dist/layout-engine/keep-together.d.ts +15 -6
- package/dist/layout-engine/keep-together.js +43 -16
- package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
- package/dist/layout-engine/layoutInstrumentation.js +8 -1
- package/dist/layout-engine/measure/advanceComposition.js +50 -7
- package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
- package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
- package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
- package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
- package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
- package/dist/layout-engine/measure/lineBreaks.js +10 -3
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
- package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
- package/dist/layout-engine/measure/measureBlocks.js +46 -10
- package/dist/layout-engine/measure/measureContainer.js +51 -8
- package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
- package/dist/layout-engine/measure/measureHelpers.js +34 -8
- package/dist/layout-engine/measure/measureParagraph.js +143 -101
- package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
- package/dist/layout-engine/measure/tabCalculator.js +2 -1
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +51 -19
- package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
- package/dist/layout-engine/measure/tableCellGrid.js +45 -5
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
- package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
- package/dist/layout-engine/noteAreaFlow.js +83 -0
- package/dist/layout-engine/paginator.d.ts +12 -1
- package/dist/layout-engine/paginator.js +18 -2
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +25 -2
- package/dist/layout-engine/types.d.ts +108 -3
- package/dist/layout-engine/types.js +96 -1
- package/dist/layout-painter/index.d.ts +3 -0
- package/dist/layout-painter/renderPage.js +32 -83
- package/dist/layout-painter/renderParagraph.d.ts +6 -1
- package/dist/layout-painter/renderParagraph.js +75 -26
- package/dist/layout-painter/renderTable.js +26 -13
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.d.ts +9 -9
- package/dist/prosemirror/attrs/index.js +133 -11
- package/dist/prosemirror/commands/comments.js +51 -41
- package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
- package/dist/prosemirror/contentControlRevisions.js +84 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
- package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
- package/dist/prosemirror/conversion/markInterner.js +90 -0
- package/dist/prosemirror/conversion/toProseDoc.js +117 -58
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
- package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +4 -2
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/paragraphDirection.d.ts +29 -2
- package/dist/prosemirror/paragraphDirection.js +21 -2
- package/dist/prosemirror/plugins/suggestionMode.js +37 -8
- package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
- package/dist/prosemirror/rejoinRunCarriers.js +66 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
- package/dist/prosemirror/schema/nodes.d.ts +50 -6
- package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
- package/dist/prosemirror/textBoxHostParagraph.js +21 -0
- package/dist/prosemirror/validation.js +91 -80
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/createDocument.js +10 -1
- package/dist/utils/scriptSegments.d.ts +33 -4
- package/dist/utils/scriptSegments.js +39 -6
- package/dist/utils/textFormattingMerge.d.ts +17 -3
- package/dist/utils/textFormattingMerge.js +18 -12
- package/dist/utils/trailingText.d.ts +22 -0
- package/dist/utils/trailingText.js +32 -0
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- package/dist/layout-engine/justifiedLineFit.js +0 -6
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { WordDiffSegment } from "./word-diff.js";
|
|
2
|
+
//#region src/ai-edits/minimal-replacement.d.ts
|
|
3
|
+
/** Replace `source.slice(start, end)` with `text`; offsets index the source span. */
|
|
4
|
+
type TextChange = {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
text: string;
|
|
8
|
+
};
|
|
9
|
+
/** A clean-text span that can only be replaced whole (a field result). */
|
|
10
|
+
type AtomicTextSpan = {
|
|
11
|
+
offset: number;
|
|
12
|
+
length: number;
|
|
13
|
+
};
|
|
14
|
+
/** Common prefix length that never ends between the halves of a surrogate pair. */
|
|
15
|
+
declare const commonPrefixLength: (a: string, b: string) => number;
|
|
16
|
+
/** Common suffix length that never starts between the halves of a surrogate pair. */
|
|
17
|
+
declare const commonSuffixLength: (a: string, b: string) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Edits beyond which a replacement is treated as one rewrite. Bounds the diff
|
|
20
|
+
* at O((N + M) * D) time and O(D^2) memory; a rewrite that large keeps
|
|
21
|
+
* nothing of its source worth aligning to anyway.
|
|
22
|
+
*/
|
|
23
|
+
declare const MAX_TOKEN_EDITS = 1024;
|
|
24
|
+
/**
|
|
25
|
+
* The shortest edit script between two token sequences (Myers, 1986), as
|
|
26
|
+
* segments of joined token text, or `null` past `maxEdits` edits.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately the shortest one. The redline diff in `word-diff.ts` gives up
|
|
29
|
+
* short matches between changes so a reader sees whole phrases replaced; an
|
|
30
|
+
* edit applied directly has no reader to serve, and every token it gives up is
|
|
31
|
+
* text rewritten with someone else's formatting.
|
|
32
|
+
*/
|
|
33
|
+
declare const shortestTokenDiff: (before: readonly string[], after: readonly string[], maxEdits?: number) => WordDiffSegment[] | null;
|
|
34
|
+
/**
|
|
35
|
+
* The changes from `source` to `replacement`, in source order and disjoint.
|
|
36
|
+
*
|
|
37
|
+
* Words (with their leading whitespace, and edge punctuation on its own; see
|
|
38
|
+
* `tokenizeWords`) are aligned by the shortest edit script, and each changed
|
|
39
|
+
* stretch is then trimmed to the characters that actually differ, so
|
|
40
|
+
* appending a letter to a word that spans two runs leaves both runs alone. A
|
|
41
|
+
* rewrite past {@link MAX_TOKEN_EDITS}, or a diff that does not reconstruct
|
|
42
|
+
* both strings exactly (it never should), is one change over the whole span,
|
|
43
|
+
* trimmed the same way.
|
|
44
|
+
*/
|
|
45
|
+
declare const planTextChanges: (source: string, replacement: string) => TextChange[];
|
|
46
|
+
/**
|
|
47
|
+
* The changes a diff's segments describe, in source order and disjoint: each
|
|
48
|
+
* run of `del` and `ins` segments between two `equal` ones is one change,
|
|
49
|
+
* exactly as wide as the segments say. A redline's segments come cut to the
|
|
50
|
+
* granularity its reader asked for, so they are not trimmed further.
|
|
51
|
+
*/
|
|
52
|
+
declare const changesFromSegments: (segments: readonly WordDiffSegment[]) => TextChange[];
|
|
53
|
+
/**
|
|
54
|
+
* `changes` widened so none cuts into an atomic span, then merged where the
|
|
55
|
+
* widening made two overlap. A widened change re-emits the source text it
|
|
56
|
+
* absorbed, so applying the result still yields exactly the replacement.
|
|
57
|
+
*
|
|
58
|
+
* `spans` are in the same coordinates as the changes (the source span's).
|
|
59
|
+
*/
|
|
60
|
+
declare const widenChangesToAtomicSpans: (source: string, changes: readonly TextChange[], spans: readonly AtomicTextSpan[]) => TextChange[];
|
|
61
|
+
/** `source` with `changes` applied; the invariant the planner owes. */
|
|
62
|
+
declare const applyTextChanges: (source: string, changes: readonly TextChange[]) => string;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { AtomicTextSpan, MAX_TOKEN_EDITS, TextChange, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { tokenizeWords } from "./word-diff.js";
|
|
2
|
+
//#region src/ai-edits/minimal-replacement.ts
|
|
3
|
+
/**
|
|
4
|
+
* The smallest set of text changes that turns one clean-text span into another.
|
|
5
|
+
*
|
|
6
|
+
* A direct-mode replacement (`replaceInBlock`, `replaceRange`, `replaceBlock`)
|
|
7
|
+
* names the text it matched and the text it wants. Swapping the whole match
|
|
8
|
+
* gives every character of the result the formatting of the first one, drops
|
|
9
|
+
* the inline content controls, note references and fields the match crossed,
|
|
10
|
+
* and makes a one-character edit rewrite the paragraph. The edit is the
|
|
11
|
+
* difference between the two strings, so that is all the applier writes:
|
|
12
|
+
*
|
|
13
|
+
* - Text outside a change is not touched: its runs, marks and every inline node
|
|
14
|
+
* between its characters stay exactly as they were.
|
|
15
|
+
* - A change deletes only the characters it removes (text, tabs, breaks and
|
|
16
|
+
* whole field results). Non-text inline content between those characters —
|
|
17
|
+
* bookmarks, comment anchors, drawings, content-control boundaries, rendered
|
|
18
|
+
* page breaks — stays where it was; the new text is written where the first
|
|
19
|
+
* removed character stood and takes its formatting.
|
|
20
|
+
* - A pure insertion follows the character before it, with the formatting that
|
|
21
|
+
* character has, the way typed text does. At the start of the matched span
|
|
22
|
+
* there is no such character inside the match, so it takes the formatting of
|
|
23
|
+
* the first matched character. A note reference's number lends no
|
|
24
|
+
* formatting: prose beside it is not superscript.
|
|
25
|
+
* - An insertion at the edge of an inline content control (`w:sdt`), a link or
|
|
26
|
+
* a comment range stays inside it only when the whole match lies inside it.
|
|
27
|
+
* Matching a control's or a link's text edits it; appending to a paragraph
|
|
28
|
+
* that ends in a checkbox or a link writes after it.
|
|
29
|
+
* - A field result is one unit: a change that would cut into it is widened to
|
|
30
|
+
* the whole result, which the new text then replaces as plain text (a result
|
|
31
|
+
* cannot be partly edited; it is regenerated from the field code). A field
|
|
32
|
+
* whose displayed text the replacement keeps is never touched. A match that
|
|
33
|
+
* itself starts or ends inside a field result is refused as before
|
|
34
|
+
* (`unsupportedBlock`).
|
|
35
|
+
*
|
|
36
|
+
* A replacement carrying inline emphasis markup (`**bold**`) states its own
|
|
37
|
+
* formatting and still replaces the match whole in direct mode.
|
|
38
|
+
*
|
|
39
|
+
* Tracked-changes and suggested modes cut their changes from the redline diff
|
|
40
|
+
* instead ({@link changesFromSegments}: word or character granularity, as the
|
|
41
|
+
* caller asked), and map them onto the document by the same rules. A change
|
|
42
|
+
* marks exactly the characters it removes as a deletion, which keeps their
|
|
43
|
+
* runs, and writes its text as an insertion after them, formatted as the
|
|
44
|
+
* first removed character that is not a note reference; a pure insertion is
|
|
45
|
+
* placed and formatted as above. Nothing outside a change is wrapped in a
|
|
46
|
+
* revision.
|
|
47
|
+
*
|
|
48
|
+
* This module is the pure half: clean-text offsets in, changes out. The
|
|
49
|
+
* applier maps each change onto the document.
|
|
50
|
+
*/
|
|
51
|
+
const isHighSurrogate = (code) => code >= 55296 && code <= 56319;
|
|
52
|
+
const isLowSurrogate = (code) => code >= 56320 && code <= 57343;
|
|
53
|
+
/** Common prefix length that never ends between the halves of a surrogate pair. */
|
|
54
|
+
const commonPrefixLength = (a, b) => {
|
|
55
|
+
const limit = Math.min(a.length, b.length);
|
|
56
|
+
let index = 0;
|
|
57
|
+
while (index < limit && a.charCodeAt(index) === b.charCodeAt(index)) index++;
|
|
58
|
+
return index > 0 && isHighSurrogate(a.charCodeAt(index - 1)) ? index - 1 : index;
|
|
59
|
+
};
|
|
60
|
+
/** Common suffix length that never starts between the halves of a surrogate pair. */
|
|
61
|
+
const commonSuffixLength = (a, b) => {
|
|
62
|
+
const limit = Math.min(a.length, b.length);
|
|
63
|
+
let length = 0;
|
|
64
|
+
while (length < limit && a.charCodeAt(a.length - 1 - length) === b.charCodeAt(b.length - 1 - length)) length++;
|
|
65
|
+
return length > 0 && isLowSurrogate(a.charCodeAt(a.length - length)) ? length - 1 : length;
|
|
66
|
+
};
|
|
67
|
+
/** `change` without the characters its deleted and inserted text share at either end. */
|
|
68
|
+
const trimChange = (source, change) => {
|
|
69
|
+
const removed = source.slice(change.start, change.end);
|
|
70
|
+
const prefix = commonPrefixLength(removed, change.text);
|
|
71
|
+
const suffix = commonSuffixLength(removed.slice(prefix), change.text.slice(prefix));
|
|
72
|
+
const trimmed = {
|
|
73
|
+
start: change.start + prefix,
|
|
74
|
+
end: change.end - suffix,
|
|
75
|
+
text: change.text.slice(prefix, change.text.length - suffix)
|
|
76
|
+
};
|
|
77
|
+
return trimmed.start === trimmed.end && trimmed.text.length === 0 ? null : trimmed;
|
|
78
|
+
};
|
|
79
|
+
const reconstructs = (segments, source, replacement) => {
|
|
80
|
+
let before = "";
|
|
81
|
+
let after = "";
|
|
82
|
+
for (const segment of segments) {
|
|
83
|
+
if (segment.type !== "ins") before += segment.text;
|
|
84
|
+
if (segment.type !== "del") after += segment.text;
|
|
85
|
+
}
|
|
86
|
+
return before === source && after === replacement;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Edits beyond which a replacement is treated as one rewrite. Bounds the diff
|
|
90
|
+
* at O((N + M) * D) time and O(D^2) memory; a rewrite that large keeps
|
|
91
|
+
* nothing of its source worth aligning to anyway.
|
|
92
|
+
*/
|
|
93
|
+
const MAX_TOKEN_EDITS = 1024;
|
|
94
|
+
/**
|
|
95
|
+
* The shortest edit script between two token sequences (Myers, 1986), as
|
|
96
|
+
* segments of joined token text, or `null` past `maxEdits` edits.
|
|
97
|
+
*
|
|
98
|
+
* Deliberately the shortest one. The redline diff in `word-diff.ts` gives up
|
|
99
|
+
* short matches between changes so a reader sees whole phrases replaced; an
|
|
100
|
+
* edit applied directly has no reader to serve, and every token it gives up is
|
|
101
|
+
* text rewritten with someone else's formatting.
|
|
102
|
+
*/
|
|
103
|
+
const shortestTokenDiff = (before, after, maxEdits = MAX_TOKEN_EDITS) => {
|
|
104
|
+
const n = before.length;
|
|
105
|
+
const m = after.length;
|
|
106
|
+
const limit = Math.min(n + m, maxEdits);
|
|
107
|
+
const offset = limit + 1;
|
|
108
|
+
const frontier = new Int32Array(2 * limit + 3);
|
|
109
|
+
const trace = [];
|
|
110
|
+
let edits = -1;
|
|
111
|
+
search: for (let d = 0; d <= limit; d++) {
|
|
112
|
+
trace.push(frontier.slice(offset - d, offset + d + 1));
|
|
113
|
+
for (let k = -d; k <= d; k += 2) {
|
|
114
|
+
let x = k === -d || k !== d && frontier[offset + k - 1] < frontier[offset + k + 1] ? frontier[offset + k + 1] : frontier[offset + k - 1] + 1;
|
|
115
|
+
let y = x - k;
|
|
116
|
+
while (x < n && y < m && before[x] === after[y]) {
|
|
117
|
+
x++;
|
|
118
|
+
y++;
|
|
119
|
+
}
|
|
120
|
+
frontier[offset + k] = x;
|
|
121
|
+
if (x >= n && y >= m) {
|
|
122
|
+
edits = d;
|
|
123
|
+
break search;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (edits < 0) return null;
|
|
128
|
+
const reversed = [];
|
|
129
|
+
const push = (type, text) => {
|
|
130
|
+
const last = reversed.at(-1);
|
|
131
|
+
if (last?.type === type) last.text = text + last.text;
|
|
132
|
+
else reversed.push({
|
|
133
|
+
type,
|
|
134
|
+
text
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
let x = n;
|
|
138
|
+
let y = m;
|
|
139
|
+
for (let d = edits; d > 0; d--) {
|
|
140
|
+
const previous = trace[d];
|
|
141
|
+
const at = (diagonal) => previous[diagonal + d];
|
|
142
|
+
const k = x - y;
|
|
143
|
+
const down = k === -d || k !== d && at(k - 1) < at(k + 1);
|
|
144
|
+
const previousK = down ? k + 1 : k - 1;
|
|
145
|
+
const previousX = at(previousK);
|
|
146
|
+
const previousY = previousX - previousK;
|
|
147
|
+
while (x > previousX && y > previousY) {
|
|
148
|
+
x--;
|
|
149
|
+
y--;
|
|
150
|
+
push("equal", before[x]);
|
|
151
|
+
}
|
|
152
|
+
if (down) {
|
|
153
|
+
y--;
|
|
154
|
+
push("ins", after[y]);
|
|
155
|
+
} else {
|
|
156
|
+
x--;
|
|
157
|
+
push("del", before[x]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
while (x > 0 && y > 0) {
|
|
161
|
+
x--;
|
|
162
|
+
y--;
|
|
163
|
+
push("equal", before[x]);
|
|
164
|
+
}
|
|
165
|
+
return reversed.toReversed();
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* The changes from `source` to `replacement`, in source order and disjoint.
|
|
169
|
+
*
|
|
170
|
+
* Words (with their leading whitespace, and edge punctuation on its own; see
|
|
171
|
+
* `tokenizeWords`) are aligned by the shortest edit script, and each changed
|
|
172
|
+
* stretch is then trimmed to the characters that actually differ, so
|
|
173
|
+
* appending a letter to a word that spans two runs leaves both runs alone. A
|
|
174
|
+
* rewrite past {@link MAX_TOKEN_EDITS}, or a diff that does not reconstruct
|
|
175
|
+
* both strings exactly (it never should), is one change over the whole span,
|
|
176
|
+
* trimmed the same way.
|
|
177
|
+
*/
|
|
178
|
+
const planTextChanges = (source, replacement) => {
|
|
179
|
+
if (source === replacement) return [];
|
|
180
|
+
const diffed = shortestTokenDiff(tokenizeWords(source), tokenizeWords(replacement));
|
|
181
|
+
const segments = diffed !== null && reconstructs(diffed, source, replacement) ? diffed : [{
|
|
182
|
+
type: "del",
|
|
183
|
+
text: source
|
|
184
|
+
}, {
|
|
185
|
+
type: "ins",
|
|
186
|
+
text: replacement
|
|
187
|
+
}];
|
|
188
|
+
return changesFromSegments(segments).flatMap((change) => trimChange(source, change) ?? []);
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* The changes a diff's segments describe, in source order and disjoint: each
|
|
192
|
+
* run of `del` and `ins` segments between two `equal` ones is one change,
|
|
193
|
+
* exactly as wide as the segments say. A redline's segments come cut to the
|
|
194
|
+
* granularity its reader asked for, so they are not trimmed further.
|
|
195
|
+
*/
|
|
196
|
+
const changesFromSegments = (segments) => {
|
|
197
|
+
const changes = [];
|
|
198
|
+
let cursor = 0;
|
|
199
|
+
let pending = null;
|
|
200
|
+
for (const segment of segments) {
|
|
201
|
+
if (segment.type === "equal") {
|
|
202
|
+
if (pending !== null) {
|
|
203
|
+
changes.push(pending);
|
|
204
|
+
pending = null;
|
|
205
|
+
}
|
|
206
|
+
cursor += segment.text.length;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
pending ??= {
|
|
210
|
+
start: cursor,
|
|
211
|
+
end: cursor,
|
|
212
|
+
text: ""
|
|
213
|
+
};
|
|
214
|
+
if (segment.type === "del") {
|
|
215
|
+
cursor += segment.text.length;
|
|
216
|
+
pending.end = cursor;
|
|
217
|
+
} else pending.text += segment.text;
|
|
218
|
+
}
|
|
219
|
+
if (pending !== null) changes.push(pending);
|
|
220
|
+
return changes;
|
|
221
|
+
};
|
|
222
|
+
const cutsInto = ({ offset, length }, position) => position > offset && position < offset + length;
|
|
223
|
+
/**
|
|
224
|
+
* `changes` widened so none cuts into an atomic span, then merged where the
|
|
225
|
+
* widening made two overlap. A widened change re-emits the source text it
|
|
226
|
+
* absorbed, so applying the result still yields exactly the replacement.
|
|
227
|
+
*
|
|
228
|
+
* `spans` are in the same coordinates as the changes (the source span's).
|
|
229
|
+
*/
|
|
230
|
+
const widenChangesToAtomicSpans = (source, changes, spans) => {
|
|
231
|
+
if (spans.length === 0 || changes.length === 0) return [...changes];
|
|
232
|
+
const windows = changes.map(({ start, end }) => {
|
|
233
|
+
let from = start;
|
|
234
|
+
let to = end;
|
|
235
|
+
for (const span of spans) {
|
|
236
|
+
if (cutsInto(span, from)) from = span.offset;
|
|
237
|
+
if (cutsInto(span, to)) to = span.offset + span.length;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
from,
|
|
241
|
+
to
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
const merged = [];
|
|
245
|
+
let index = 0;
|
|
246
|
+
while (index < changes.length) {
|
|
247
|
+
const { from } = windows[index];
|
|
248
|
+
let { to } = windows[index];
|
|
249
|
+
const group = [changes[index]];
|
|
250
|
+
index++;
|
|
251
|
+
while (index < changes.length && windows[index].from < to) {
|
|
252
|
+
to = Math.max(to, windows[index].to);
|
|
253
|
+
group.push(changes[index]);
|
|
254
|
+
index++;
|
|
255
|
+
}
|
|
256
|
+
let text = "";
|
|
257
|
+
let cursor = from;
|
|
258
|
+
for (const change of group) {
|
|
259
|
+
text += source.slice(cursor, change.start) + change.text;
|
|
260
|
+
cursor = change.end;
|
|
261
|
+
}
|
|
262
|
+
text += source.slice(cursor, to);
|
|
263
|
+
merged.push({
|
|
264
|
+
start: from,
|
|
265
|
+
end: to,
|
|
266
|
+
text
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return merged;
|
|
270
|
+
};
|
|
271
|
+
/** `source` with `changes` applied; the invariant the planner owes. */
|
|
272
|
+
const applyTextChanges = (source, changes) => {
|
|
273
|
+
let result = "";
|
|
274
|
+
let cursor = 0;
|
|
275
|
+
for (const change of changes) {
|
|
276
|
+
result += source.slice(cursor, change.start) + change.text;
|
|
277
|
+
cursor = change.end;
|
|
278
|
+
}
|
|
279
|
+
return result + source.slice(cursor);
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
export { MAX_TOKEN_EDITS, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildFontAlternates, getFontAlternate } from "../fonts/fontAlternates.js";
|
|
2
|
+
import { DEFAULT_FONT_FAMILY } from "../layout-engine/measure/measureHelpers.js";
|
|
2
3
|
import { expectFontFamilyMarkAttrs, expectParagraphAttrs } from "../prosemirror/attrs/index.js";
|
|
3
4
|
import { parseFontFamilyList, resolveFontFamily } from "../utils/fontResolver.js";
|
|
4
5
|
//#region src/controller/fontReadiness.ts
|
|
@@ -11,7 +12,6 @@ function documentFontsAreLoaded() {
|
|
|
11
12
|
return !fontSet || fontSet.status === "loaded";
|
|
12
13
|
}
|
|
13
14
|
const INITIAL_LAYOUT_FONT_TIMEOUT_MS = 2e3;
|
|
14
|
-
const DEFAULT_LAYOUT_FONT_FAMILY = "Calibri";
|
|
15
15
|
const CSS_GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
|
|
16
16
|
"serif",
|
|
17
17
|
"sans-serif",
|
|
@@ -54,7 +54,7 @@ function collectInitialLayoutFontFamilies(documentModel, pmDoc) {
|
|
|
54
54
|
function collectInitialLayoutFontFaces(documentModel, pmDoc) {
|
|
55
55
|
const faces = /* @__PURE__ */ new Map();
|
|
56
56
|
const fontAlternates = buildFontAlternates(documentModel?.package.fontTable);
|
|
57
|
-
addLayoutFontFamilyFace(faces,
|
|
57
|
+
addLayoutFontFamilyFace(faces, DEFAULT_FONT_FAMILY, REGULAR_LAYOUT_FONT_DESCRIPTOR, fontAlternates);
|
|
58
58
|
for (const family of documentModel?.requiredFonts ?? []) addLayoutFontFamilyFace(faces, family, REGULAR_LAYOUT_FONT_DESCRIPTOR, fontAlternates);
|
|
59
59
|
addLayoutFontFamilyFace(faces, documentModel?.package.theme?.fontScheme?.majorFont?.latin, REGULAR_LAYOUT_FONT_DESCRIPTOR, fontAlternates);
|
|
60
60
|
addLayoutFontFamilyFace(faces, documentModel?.package.theme?.fontScheme?.minorFont?.latin, REGULAR_LAYOUT_FONT_DESCRIPTOR, fontAlternates);
|
|
@@ -75,7 +75,7 @@ function collectProseMirrorFontFaces(faces, node, inheritedTextFormatting, fontA
|
|
|
75
75
|
if (node.type.name === "paragraph") addTextFormattingFontFaces(faces, expectParagraphAttrs(node).listMarkerFormatting, fontAlternates);
|
|
76
76
|
if (node.isText) {
|
|
77
77
|
const descriptor = layoutDescriptorFromFormattingAndMarks(textFormatting, node.marks);
|
|
78
|
-
addLayoutFontFamilyFace(faces, readFontFamilyMarkAttrs(node.marks) ?? textFormatting?.fontFamily ??
|
|
78
|
+
addLayoutFontFamilyFace(faces, readFontFamilyMarkAttrs(node.marks) ?? textFormatting?.fontFamily ?? "Times New Roman", descriptor, fontAlternates);
|
|
79
79
|
}
|
|
80
80
|
node.forEach((child) => {
|
|
81
81
|
collectProseMirrorFontFaces(faces, child, textFormatting, fontAlternates);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HyphenationDictionaryError, HyphenationDictionaryId } from "../layout-engine/measure/hyphenationDictionaries.js";
|
|
2
|
+
//#region src/controller/hyphenationReadiness.d.ts
|
|
3
|
+
type HyphenationReadiness = {
|
|
4
|
+
/** Follow up on the dictionaries one layout run lacked. */
|
|
5
|
+
track: (missing: ReadonlySet<HyphenationDictionaryId>) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Ignore loads still pending, as on unmount. A later `track` follows up
|
|
8
|
+
* afresh, so an adapter whose lifecycle re-attaches (React Strict Mode
|
|
9
|
+
* effects) keeps working.
|
|
10
|
+
*/
|
|
11
|
+
cancel: () => void;
|
|
12
|
+
};
|
|
13
|
+
type HyphenationReadinessOptions = {
|
|
14
|
+
relayout: () => void;
|
|
15
|
+
onError: (error: HyphenationDictionaryError) => void;
|
|
16
|
+
};
|
|
17
|
+
declare const createHyphenationReadiness: ({ relayout, onError }: HyphenationReadinessOptions) => HyphenationReadiness;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { HyphenationReadiness, HyphenationReadinessOptions, createHyphenationReadiness };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { requestHyphenationDictionary } from "../layout-engine/measure/hyphenationDictionaries.js";
|
|
2
|
+
//#region src/controller/hyphenationReadiness.ts
|
|
3
|
+
/**
|
|
4
|
+
* Per-editor follow-up for hyphenation dictionaries a layout run lacked.
|
|
5
|
+
*
|
|
6
|
+
* Framework-neutral so both adapters share one implementation. The layout
|
|
7
|
+
* pipeline hands {@link HyphenationReadiness.track} the dictionaries its run
|
|
8
|
+
* requested but could not use; this editor then re-runs layout when one loads
|
|
9
|
+
* (the load has already invalidated measured paragraphs) and reports a failed
|
|
10
|
+
* one once. Editors that never asked for a dictionary hear nothing about it,
|
|
11
|
+
* and an editor mounted after a failure still learns of it on its first run.
|
|
12
|
+
*/
|
|
13
|
+
const createHyphenationReadiness = ({ relayout, onError }) => {
|
|
14
|
+
let epoch = 0;
|
|
15
|
+
const pending = /* @__PURE__ */ new Set();
|
|
16
|
+
const reported = /* @__PURE__ */ new Set();
|
|
17
|
+
const follow = async (dictionary) => {
|
|
18
|
+
const startedIn = epoch;
|
|
19
|
+
pending.add(dictionary);
|
|
20
|
+
const settled = await requestHyphenationDictionary(dictionary);
|
|
21
|
+
if (startedIn !== epoch) return;
|
|
22
|
+
pending.delete(dictionary);
|
|
23
|
+
switch (settled.status) {
|
|
24
|
+
case "loaded":
|
|
25
|
+
relayout();
|
|
26
|
+
return;
|
|
27
|
+
case "failed":
|
|
28
|
+
if (!reported.has(dictionary)) {
|
|
29
|
+
reported.add(dictionary);
|
|
30
|
+
onError(settled.error);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
default:
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
track: (missing) => {
|
|
38
|
+
for (const dictionary of missing) if (!pending.has(dictionary)) follow(dictionary);
|
|
39
|
+
},
|
|
40
|
+
cancel: () => {
|
|
41
|
+
epoch += 1;
|
|
42
|
+
pending.clear();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createHyphenationReadiness };
|
|
@@ -3,6 +3,7 @@ import { TemplatePreviewEntry, TemplatePreviewHiddenRange } from "../prosemirror
|
|
|
3
3
|
import { ColumnLayout, FlowBlock, FootnoteContent, HeaderFooterContent, Layout, Measure, PageHeaderFooterRefs, PageMargins } from "../layout-engine/types.js";
|
|
4
4
|
import { LayoutRunReason } from "../layout-engine/layoutInstrumentation.js";
|
|
5
5
|
import { DirtyRange } from "../paged-layout/incrementalMeasure.js";
|
|
6
|
+
import { HyphenationReadiness } from "./hyphenationReadiness.js";
|
|
6
7
|
import { ConvertHeaderFooterOptions, HeaderFooterMetrics } from "../layout-bridge/convert/headerFooterLayout.js";
|
|
7
8
|
import "../layout-engine/index.js";
|
|
8
9
|
import { FootnoteRenderItem } from "../layout-painter/renderPage.js";
|
|
@@ -74,6 +75,8 @@ type LayoutPipelineDeps<THfPMs> = {
|
|
|
74
75
|
pageRenderer?: PageRendererName;
|
|
75
76
|
emptyTemplatePreviewEntries: readonly TemplatePreviewEntry[];
|
|
76
77
|
emptyTemplatePreviewHidden: readonly TemplatePreviewHiddenRange[];
|
|
78
|
+
/** Follows up on the hyphenation dictionaries a run lacked (relayout or error). */
|
|
79
|
+
hyphenationReadiness: HyphenationReadiness;
|
|
77
80
|
};
|
|
78
81
|
declare function runLayoutPipeline<THfPMs>(deps: LayoutPipelineDeps<THfPMs>, state: EditorState, options?: LayoutRunOptions): LayoutOutcome;
|
|
79
82
|
//#endregion
|
|
@@ -9,13 +9,16 @@ import { buildHeaderFooterFieldValues, fieldValuesEqual, resolveFieldValues } fr
|
|
|
9
9
|
import { buildSectionPageCounts } from "../fields/sectionPageCounts.js";
|
|
10
10
|
import { buildSeqValues } from "../fields/seqValues.js";
|
|
11
11
|
import { buildFontAlternates } from "../fonts/fontAlternates.js";
|
|
12
|
+
import { prepareEndnoteAreas, resolveEndnotePosition, spliceEndnoteAreas, trailingEndnoteIds } from "../layout-bridge/convert/endnoteLayout.js";
|
|
12
13
|
import { buildFootnoteContentMap, collectEndnoteRefs, collectFootnoteRefs, computeNoteDisplayNumbers, remapNoteMarkerText } from "../layout-bridge/convert/footnoteLayout.js";
|
|
13
14
|
import { applyTemplatePreviewToBlocks } from "../layout-bridge/convert/templatePreviewFlow.js";
|
|
14
15
|
import { toFlowBlocks } from "../layout-bridge/convert/toFlowBlocks.js";
|
|
15
16
|
import { getColumns } from "../layout-bridge/sectionColumns.js";
|
|
17
|
+
import { resolveAnchorLayoutInCellCompatibility } from "../layout-engine/anchorLayoutInCellCompatibility.js";
|
|
16
18
|
import { layoutDocument } from "../layout-engine/index.js";
|
|
17
19
|
import { resolveJustificationCompatibility } from "../layout-engine/justificationCompatibility.js";
|
|
18
20
|
import { recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart } from "../layout-engine/layoutInstrumentation.js";
|
|
21
|
+
import { collectRequestedHyphenationDictionaries } from "../layout-engine/measure/hyphenationDictionaries.js";
|
|
19
22
|
import { measureBlocks, measureSingleBlockWithoutFloatingZones } from "../layout-engine/measure/measureBlocks.js";
|
|
20
23
|
import { installCanvasMeasureProvider } from "../layout-engine/measure/measureContainer.js";
|
|
21
24
|
import { resolveEffectiveParagraphSpacingTree } from "../layout-engine/paragraphSpacing.js";
|
|
@@ -100,6 +103,11 @@ function attachAuthoredMarginsToLayoutPages(layout, options) {
|
|
|
100
103
|
} : layout;
|
|
101
104
|
}
|
|
102
105
|
function runLayoutPipeline(deps, state, options = {}) {
|
|
106
|
+
const { result, missing } = collectRequestedHyphenationDictionaries(() => runLayoutPipelineMeasured(deps, state, options));
|
|
107
|
+
deps.hyphenationReadiness.track(missing);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function runLayoutPipelineMeasured(deps, state, options) {
|
|
103
111
|
const { contentWidth, columns, pageSize, margins, pageGap, showMarginGuides, marginGuideColor, syncCoordinator, headerContent, footerContent, firstPageHeaderContent, firstPageFooterContent, headerContentRId, footerContentRId, firstPageHeaderContentRId, firstPageFooterContentRId, sectionHeaderFooterRefs, theme: _theme, sectionProperties, document, defaultTabStop, mirrorMargins, styles, layout, hfPMs, painter, pagesContainer, session, renderHfFromContentOrPm, renderHeaderFooterContentByRId, documentFontsAreLoaded, buildFootnoteRenderItems, describeInvalidHighlightMarks, pageRenderer = PAGE_RENDERER.legacy, emptyTemplatePreviewEntries: EMPTY_TEMPLATE_PREVIEW_ENTRIES, emptyTemplatePreviewHidden: EMPTY_TEMPLATE_PREVIEW_HIDDEN } = deps;
|
|
104
112
|
let outcome = {};
|
|
105
113
|
let pendingArtifacts;
|
|
@@ -131,6 +139,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
131
139
|
if (justificationCompatibility) flowOpts.justificationCompatibility = justificationCompatibility;
|
|
132
140
|
const tableIndentCompatibility = resolveTableIndentCompatibility(documentSettings?.compatibilityMode);
|
|
133
141
|
if (tableIndentCompatibility) flowOpts.tableIndentCompatibility = tableIndentCompatibility;
|
|
142
|
+
if (resolveAnchorLayoutInCellCompatibility(documentSettings?.compatibilityMode)) flowOpts.forceAnchorLayoutInCell = true;
|
|
134
143
|
if (documentSettings?.autoHyphenation === true) flowOpts.automaticHyphenation = {
|
|
135
144
|
enabled: true,
|
|
136
145
|
...documentSettings.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: documentSettings.doNotHyphenateCaps } : {},
|
|
@@ -139,6 +148,9 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
139
148
|
};
|
|
140
149
|
const finalSectionDocumentGridLinePitchTwips = resolveDocumentGridLinePitch(document?.package.document.sections?.at(-1)?.properties.docGrid);
|
|
141
150
|
if (finalSectionDocumentGridLinePitchTwips !== void 0) flowOpts.finalSectionDocumentGridLinePitchTwips = finalSectionDocumentGridLinePitchTwips;
|
|
151
|
+
const endnotePosition = resolveEndnotePosition(document?.package.settings?.endnotePr?.position, document?.package.document.sections?.at(-1)?.properties);
|
|
152
|
+
const bodyTrailingEndnoteIds = trailingEndnoteIds(document?.package.endnotes, endnotePosition);
|
|
153
|
+
if (bodyTrailingEndnoteIds.size > 0) flowOpts.trailingEndnoteIds = bodyTrailingEndnoteIds;
|
|
142
154
|
let newBlocks = toFlowBlocks(document === null ? state.doc.type.create({
|
|
143
155
|
...state.doc.attrs,
|
|
144
156
|
_finalSectionStart: sectionProperties?.sectionStart ?? null
|
|
@@ -166,7 +178,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
166
178
|
const footnoteDisplayNumbers = documentFootnotes ? computeNoteDisplayNumbers(documentFootnotes, footnoteRefs.map((ref) => ref.footnoteId)) : void 0;
|
|
167
179
|
const documentEndnotes = document?.package.endnotes;
|
|
168
180
|
const endnoteDisplayNumbers = documentEndnotes ? computeNoteDisplayNumbers(documentEndnotes, collectEndnoteRefs(newBlocks).map((ref) => ref.endnoteId)) : void 0;
|
|
169
|
-
const endnoteNumberFormat = document?.package.document.sections?.at(-1)?.properties.endnotePr?.numFmt ?? "lowerRoman";
|
|
181
|
+
const endnoteNumberFormat = document?.package.document.sections?.at(-1)?.properties.endnotePr?.numFmt ?? document?.package.settings?.endnotePr?.numFmt ?? "lowerRoman";
|
|
170
182
|
const endnoteTexts = endnoteDisplayNumbers ? formatEndnoteTexts(endnoteDisplayNumbers, (displayNumber) => formatOoxmlCounter(displayNumber, endnoteNumberFormat)) : void 0;
|
|
171
183
|
newBlocks = remapNoteMarkerText(newBlocks, {
|
|
172
184
|
...footnoteDisplayNumbers ? { footnoteNumbers: footnoteDisplayNumbers } : {},
|
|
@@ -218,6 +230,15 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
218
230
|
preparedFooter: refs.footerEven ? footerContentByRId?.get(refs.footerEven) : void 0
|
|
219
231
|
});
|
|
220
232
|
});
|
|
233
|
+
const sectionFirstPageMargins = sectionHeaderFooterRefs?.map((refs, index) => {
|
|
234
|
+
if (refs.titlePg !== true) return;
|
|
235
|
+
const properties = sectionPropertiesForMargins[index];
|
|
236
|
+
return bodyMarginsClearHeaderFooter({
|
|
237
|
+
authoredMargins: properties ? getMargins(properties) : margins,
|
|
238
|
+
preparedHeader: refs.headerFirst ? headerContentByRId?.get(refs.headerFirst) : void 0,
|
|
239
|
+
preparedFooter: refs.footerFirst ? footerContentByRId?.get(refs.footerFirst) : void 0
|
|
240
|
+
});
|
|
241
|
+
});
|
|
221
242
|
newBlocks = bodyBlocksClearSectionHeaderFooter(newBlocks, {
|
|
222
243
|
authoredMargins: margins,
|
|
223
244
|
sectionHeaderFooterRefs,
|
|
@@ -302,6 +323,36 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
302
323
|
};
|
|
303
324
|
outcome.measures = newMeasures;
|
|
304
325
|
recordPhaseDuration("measure-blocks", phaseStartedAt);
|
|
326
|
+
const buildNoteOptions = () => {
|
|
327
|
+
const noteOptions = { measureBlocks };
|
|
328
|
+
if (flowOpts.styles) noteOptions.styles = flowOpts.styles;
|
|
329
|
+
if (flowOpts.defaultSize !== void 0) noteOptions.defaultSize = flowOpts.defaultSize;
|
|
330
|
+
if (_theme !== void 0) noteOptions.theme = _theme;
|
|
331
|
+
noteOptions.fontAlternates = fontAlternates;
|
|
332
|
+
if (defaultTabStop !== void 0) noteOptions.defaultTabStopTwips = defaultTabStop;
|
|
333
|
+
if (flowOpts.lineBreakRules) noteOptions.lineBreakRules = flowOpts.lineBreakRules;
|
|
334
|
+
if (flowOpts.justificationCompatibility) noteOptions.justificationCompatibility = flowOpts.justificationCompatibility;
|
|
335
|
+
if (flowOpts.tableIndentCompatibility) noteOptions.tableIndentCompatibility = flowOpts.tableIndentCompatibility;
|
|
336
|
+
if (flowOpts.automaticHyphenation) noteOptions.automaticHyphenation = flowOpts.automaticHyphenation;
|
|
337
|
+
return noteOptions;
|
|
338
|
+
};
|
|
339
|
+
const endnoteAreas = documentEndnotes && endnoteDisplayNumbers && endnoteTexts ? prepareEndnoteAreas({
|
|
340
|
+
blocks: newBlocks,
|
|
341
|
+
endnotes: documentEndnotes,
|
|
342
|
+
displayNumbers: endnoteDisplayNumbers,
|
|
343
|
+
displayTexts: endnoteTexts,
|
|
344
|
+
position: endnotePosition,
|
|
345
|
+
sections: sectionPropertiesForMargins,
|
|
346
|
+
options: buildNoteOptions()
|
|
347
|
+
}) : void 0;
|
|
348
|
+
const layoutWithNoteAreas = (measures, nextLayoutOpts) => {
|
|
349
|
+
if (endnoteAreas === void 0) return layoutDocument(newBlocks, measures, nextLayoutOpts);
|
|
350
|
+
const flow = spliceEndnoteAreas(newBlocks, measures, endnoteAreas);
|
|
351
|
+
return layoutDocument(flow.blocks, flow.measures, {
|
|
352
|
+
...nextLayoutOpts,
|
|
353
|
+
noteAreas: endnoteAreas.noteAreas
|
|
354
|
+
});
|
|
355
|
+
};
|
|
305
356
|
phaseStartedAt = performance.now();
|
|
306
357
|
let newLayout;
|
|
307
358
|
let pageFootnoteMap = /* @__PURE__ */ new Map();
|
|
@@ -316,7 +367,8 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
316
367
|
};
|
|
317
368
|
if (document?.package.document.sections !== void 0) nextLayoutOpts.sectionVerticalAlignments = document.package.document.sections.map(({ properties }) => properties.verticalAlign);
|
|
318
369
|
else if (sectionProperties !== null && sectionProperties !== void 0) nextLayoutOpts.sectionVerticalAlignments = [sectionProperties.verticalAlign];
|
|
319
|
-
if (
|
|
370
|
+
if (sectionFirstPageMargins !== void 0) nextLayoutOpts.sectionFirstPageMargins = sectionFirstPageMargins;
|
|
371
|
+
else if (hasTitlePg) nextLayoutOpts.firstPageMargins = bodyMarginsClearHeaderFooter({
|
|
320
372
|
authoredMargins: margins,
|
|
321
373
|
preparedHeader: firstPageHeaderForRender,
|
|
322
374
|
preparedFooter: firstPageFooterForRender
|
|
@@ -338,29 +390,17 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
338
390
|
const layoutOpts = buildLayoutOpts();
|
|
339
391
|
let layoutOptsUsed = layoutOpts;
|
|
340
392
|
if (hasFootnotes) {
|
|
341
|
-
footnoteContentMap = buildFootnoteContentMap(documentFootnotes, footnoteRefs, contentWidth, (
|
|
342
|
-
const footnoteOptions = { measureBlocks };
|
|
343
|
-
if (flowOpts.styles) footnoteOptions.styles = flowOpts.styles;
|
|
344
|
-
if (flowOpts.defaultSize !== void 0) footnoteOptions.defaultSize = flowOpts.defaultSize;
|
|
345
|
-
if (_theme !== void 0) footnoteOptions.theme = _theme;
|
|
346
|
-
footnoteOptions.fontAlternates = fontAlternates;
|
|
347
|
-
if (defaultTabStop !== void 0) footnoteOptions.defaultTabStopTwips = defaultTabStop;
|
|
348
|
-
if (flowOpts.lineBreakRules) footnoteOptions.lineBreakRules = flowOpts.lineBreakRules;
|
|
349
|
-
if (flowOpts.justificationCompatibility) footnoteOptions.justificationCompatibility = flowOpts.justificationCompatibility;
|
|
350
|
-
if (flowOpts.tableIndentCompatibility) footnoteOptions.tableIndentCompatibility = flowOpts.tableIndentCompatibility;
|
|
351
|
-
if (flowOpts.automaticHyphenation) footnoteOptions.automaticHyphenation = flowOpts.automaticHyphenation;
|
|
352
|
-
return footnoteOptions;
|
|
353
|
-
})());
|
|
393
|
+
footnoteContentMap = buildFootnoteContentMap(documentFootnotes, footnoteRefs, contentWidth, buildNoteOptions());
|
|
354
394
|
const footnoteHeightById = /* @__PURE__ */ new Map();
|
|
355
395
|
for (const [id, content] of footnoteContentMap) footnoteHeightById.set(id, content.height + 0);
|
|
356
396
|
layoutOptsUsed = {
|
|
357
397
|
...layoutOpts,
|
|
358
398
|
footnoteHeightById
|
|
359
399
|
};
|
|
360
|
-
newLayout =
|
|
400
|
+
newLayout = layoutWithNoteAreas(newMeasures, layoutOptsUsed);
|
|
361
401
|
pageFootnoteMap = /* @__PURE__ */ new Map();
|
|
362
402
|
for (const page of newLayout.pages) if (page.footnoteIds && page.footnoteIds.length > 0) pageFootnoteMap.set(page.number, page.footnoteIds);
|
|
363
|
-
} else newLayout =
|
|
403
|
+
} else newLayout = layoutWithNoteAreas(newMeasures, layoutOpts);
|
|
364
404
|
const rebuildFootnotePageMap = () => {
|
|
365
405
|
pageFootnoteMap = /* @__PURE__ */ new Map();
|
|
366
406
|
for (const page of newLayout.pages) if (page.footnoteIds && page.footnoteIds.length > 0) pageFootnoteMap.set(page.number, page.footnoteIds);
|
|
@@ -376,7 +416,7 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
376
416
|
};
|
|
377
417
|
const relayoutWithCurrentMeasures = (nextLayoutOpts) => {
|
|
378
418
|
layoutOptsUsed = withFootnoteHeights(nextLayoutOpts);
|
|
379
|
-
newLayout =
|
|
419
|
+
newLayout = layoutWithNoteAreas(newMeasures, layoutOptsUsed);
|
|
380
420
|
if (hasFootnotes) rebuildFootnotePageMap();
|
|
381
421
|
};
|
|
382
422
|
const stabilizeFieldWidths = () => {
|
|
@@ -473,6 +513,18 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
473
513
|
measure
|
|
474
514
|
});
|
|
475
515
|
}
|
|
516
|
+
for (const area of endnoteAreas?.areas ?? []) for (const [index, block] of area.blocks.entries()) {
|
|
517
|
+
const measure = area.measures[index];
|
|
518
|
+
if (!measure) continue;
|
|
519
|
+
const noteStory = endnoteAreas?.noteStoryByBlockId.get(String(block.id));
|
|
520
|
+
blockLookup.set(String(block.id), {
|
|
521
|
+
block,
|
|
522
|
+
measure,
|
|
523
|
+
...noteStory === void 0 ? {} : { noteStory }
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const continuationSeparator = endnoteAreas?.noteAreas.continuationSeparator;
|
|
527
|
+
if (continuationSeparator) blockLookup.set(String(continuationSeparator.block.id), continuationSeparator);
|
|
476
528
|
outcome.blockLookup = blockLookup;
|
|
477
529
|
const footnotesByPage = hasFootnotes ? buildFootnoteRenderItems(pageFootnoteMap, footnoteContentMap, document) : void 0;
|
|
478
530
|
const renderOpts = {
|