@stll/folio-core 0.10.0 → 0.12.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 +602 -50
- package/dist/ai-edits/block-identity.d.ts +4 -0
- package/dist/ai-edits/block-identity.js +9 -0
- package/dist/ai-edits/blockRange.d.ts +28 -1
- package/dist/ai-edits/blockRange.js +67 -1
- package/dist/ai-edits/headless.d.ts +4 -4
- package/dist/ai-edits/headless.js +9 -9
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.d.ts +7 -7
- package/dist/ai-edits/read.js +110 -3
- package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
- package/dist/ai-edits/table-cell-mutations.js +237 -0
- package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
- package/dist/ai-edits/table-mutation-plan.js +90 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +100 -0
- package/dist/ai-edits/table-row-column-mutations.js +300 -0
- package/dist/ai-edits/table-targets.d.ts +32 -0
- package/dist/ai-edits/table-targets.js +70 -0
- package/dist/ai-edits/types.d.ts +34 -0
- package/dist/compat/eigenpal.js +1 -1
- package/dist/content-controls/mutateContentControls.js +1 -1
- package/dist/controller/activeEditorStory.d.ts +36 -0
- package/dist/controller/activeEditorStory.js +30 -0
- package/dist/controller/collaborationModules.d.ts +7 -0
- package/dist/controller/collaborationModules.js +15 -0
- package/dist/controller/contentControlWidgetController.d.ts +55 -0
- package/dist/controller/contentControlWidgetController.js +141 -0
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/fontReadiness.js +2 -0
- package/dist/controller/headerFooterEditorManager.d.ts +41 -0
- package/dist/controller/headerFooterEditorManager.js +182 -0
- package/dist/controller/hiddenEditorApi.js +2 -2
- package/dist/controller/hiddenEditorManager.d.ts +13 -2
- package/dist/controller/hiddenEditorManager.js +32 -11
- package/dist/controller/layoutPipeline.d.ts +2 -0
- package/dist/controller/layoutPipeline.js +16 -1
- package/dist/controller/noteEditorManager.d.ts +36 -0
- package/dist/controller/noteEditorManager.js +210 -0
- package/dist/document-operations.d.ts +35 -3
- package/dist/document-operations.js +161 -3
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/commentParser.js +6 -1
- package/dist/docx/conformance.js +8 -1
- package/dist/docx/documentGrid.d.ts +8 -0
- package/dist/docx/documentGrid.js +8 -0
- package/dist/docx/drawingUtils.js +1 -1
- package/dist/docx/ensureParaIds.js +1 -1
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/notePropertiesParser.js +1 -1
- package/dist/docx/numberingParser.js +1 -1
- package/dist/docx/paragraphParser.js +63 -58
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/parser.js +3 -3
- package/dist/docx/rezip.js +1 -1
- package/dist/docx/runParser.js +4 -2
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sectionParser.js +1 -1
- package/dist/docx/selectiveSave.js +1 -1
- package/dist/docx/serializer/commentSerializer.js +1 -2
- package/dist/docx/serializer/documentSerializer.js +2 -2
- package/dist/docx/serializer/headerFooterSerializer.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +2 -2
- package/dist/docx/serializer/paragraphSerializer.js +7 -1
- package/dist/docx/serializer/runSerializer.js +4 -2
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/serializer/tableSerializer.d.ts +5 -4
- package/dist/docx/serializer/tableSerializer.js +17 -13
- package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
- package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
- package/dist/docx/server/boundedArchive.d.ts +14 -3
- package/dist/docx/server/boundedArchive.js +56 -11
- package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
- package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
- package/dist/docx/server/inspectDocxPackage.js +217 -0
- package/dist/docx/server/validateDocxConformance.d.ts +41 -0
- package/dist/docx/server/validateDocxConformance.js +336 -0
- package/dist/docx/settingsParser.js +11 -1
- package/dist/docx/shapeParser.js +1 -1
- package/dist/docx/styleParser.js +4 -2
- package/dist/docx/tableParser.js +18 -7
- package/dist/docx/textBoxParser.d.ts +3 -3
- package/dist/docx/textBoxParser.js +16 -12
- package/dist/docx/xmlSafety.d.ts +5 -0
- package/dist/docx/xmlSafety.js +9 -0
- package/dist/fields/resolveFieldValues.js +1 -3
- package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
- package/dist/i18n/messages/catalogs.gen.js +17 -0
- package/dist/i18n/messages/messages.gen.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
- package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +136 -20
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
- package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
- package/dist/layout-bridge/engine/clickToPosition.js +2 -2
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +43 -28
- package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
- package/dist/layout-engine/justificationCompatibility.js +8 -0
- package/dist/layout-engine/keep-together.js +11 -1
- package/dist/layout-engine/measure/cache.js +21 -5
- package/dist/layout-engine/measure/lineBreakProvider.js +53 -34
- package/dist/layout-engine/measure/measureBlocks.js +6 -3
- package/dist/layout-engine/measure/measureContainer.js +1 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +3 -3
- package/dist/layout-engine/measure/measureParagraph.js +69 -25
- package/dist/layout-engine/measure/measureProvider.js +4 -3
- package/dist/layout-engine/measure/tableCellFlow.js +5 -2
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
- package/dist/layout-engine/paragraphSpacing.js +107 -13
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +26 -13
- package/dist/layout-painter/registry/modules/textBox.js +5 -1
- package/dist/layout-painter/renderPage.d.ts +5 -2
- package/dist/layout-painter/renderPage.js +56 -12
- package/dist/layout-painter/renderParagraph.js +22 -6
- package/dist/layout-painter/renderTable.d.ts +5 -1
- package/dist/layout-painter/renderTable.js +5 -2
- package/dist/layout-painter/renderTextBox.d.ts +2 -1
- package/dist/layout-painter/renderTextBox.js +25 -14
- package/dist/managers/DocumentLoaderManager.js +1 -1
- 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/model.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +154 -18
- package/dist/prosemirror/commands/comments.d.ts +3 -3
- package/dist/prosemirror/commands/comments.js +248 -12
- package/dist/prosemirror/commands/contentControls.js +1 -1
- package/dist/prosemirror/commands/index.js +1 -1
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
- package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +286 -61
- package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
- package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/toProseDoc.js +347 -98
- package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
- package/dist/prosemirror/extensions/StarterKit.js +6 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +4 -1
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +24 -1
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
- package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
- package/dist/prosemirror/findReplaceSelection.js +57 -13
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +34 -8
- package/dist/prosemirror/plugins/contentControlWidgets.js +125 -91
- package/dist/prosemirror/plugins/selectionTracker.js +1 -0
- package/dist/prosemirror/plugins/suggestionMode.js +1 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +5 -1
- package/dist/prosemirror/schema/nodes.d.ts +64 -7
- package/dist/prosemirror/selectionState.js +1 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
- package/dist/prosemirror/tableCellMergeRevision.js +22 -0
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
- package/dist/prosemirror/validation.js +9 -1
- 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 +6 -2
- package/dist/server.js +5 -1
- package/dist/utils/findReplace.d.ts +32 -0
- package/dist/utils/findReplace.js +118 -0
- package/dist/utils/fontFamilyMerge.js +9 -11
- package/dist/utils/fontLoader.js +3 -2
- package/dist/utils/fontResolver.js +22 -4
- package/dist/utils/fontWeights.d.ts +1 -1
- package/dist/utils/fontWeights.js +1 -1
- package/dist/utils/headerFooter.js +24 -1
- package/dist/utils/paragraphFormattingMerge.js +1 -0
- package/package.json +1 -1
|
@@ -27,6 +27,13 @@ const segmenterLocale = (locale) => {
|
|
|
27
27
|
};
|
|
28
28
|
const DICTIONARY_BREAK_SCRIPT = /[\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
|
|
29
29
|
const CJK_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
30
|
+
const WORD_HANGING_PUNCTUATION_LANGUAGES = /* @__PURE__ */ new Set([
|
|
31
|
+
"ii",
|
|
32
|
+
"ja",
|
|
33
|
+
"ko",
|
|
34
|
+
"zh"
|
|
35
|
+
]);
|
|
36
|
+
const SIMPLE_BREAK_TEXT = /^[\p{Script=Latin}\p{Script=Cyrillic}\p{Script=Greek}\p{Number}\p{Punctuation}\p{White_Space}]*$/u;
|
|
30
37
|
const BREAK_AFTER_CHARACTER = /* @__PURE__ */ new Set([
|
|
31
38
|
"-",
|
|
32
39
|
"֊",
|
|
@@ -42,13 +49,10 @@ const BREAK_AFTER_CHARACTER = /* @__PURE__ */ new Set([
|
|
|
42
49
|
"",
|
|
43
50
|
""
|
|
44
51
|
]);
|
|
45
|
-
const
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"u",
|
|
50
|
-
"v",
|
|
51
|
-
"z"
|
|
52
|
+
const NONBREAKING_SPACES = /* @__PURE__ */ new Set([
|
|
53
|
+
"\xA0",
|
|
54
|
+
" ",
|
|
55
|
+
" "
|
|
52
56
|
]);
|
|
53
57
|
const DEFAULT_PROHIBITED_LINE_START = /* @__PURE__ */ new Set([
|
|
54
58
|
"!",
|
|
@@ -132,30 +136,6 @@ const isLegacyEthiopicBreakCharacter = (character, policy) => {
|
|
|
132
136
|
const codePoint = character.codePointAt(0);
|
|
133
137
|
return codePoint !== void 0 && codePoint >= 4961 && codePoint <= 4968;
|
|
134
138
|
};
|
|
135
|
-
const czechProtectedBreaks = (text, policy) => {
|
|
136
|
-
const breaks = /* @__PURE__ */ new Set();
|
|
137
|
-
if (segmenterLocale(policy?.locale) !== "cs") return breaks;
|
|
138
|
-
let tokenStart = 0;
|
|
139
|
-
let index = 0;
|
|
140
|
-
while (index < text.length) {
|
|
141
|
-
const character = firstCodePoint(text, index);
|
|
142
|
-
if (character === void 0) break;
|
|
143
|
-
if (!/\s/u.test(character)) {
|
|
144
|
-
index += character.length;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const token = text.slice(tokenStart, index);
|
|
148
|
-
const protectsNextWord = token.length === 1 && CZECH_ONE_LETTER_PREPOSITIONS.has(token.toLocaleLowerCase("cs"));
|
|
149
|
-
while (index < text.length) {
|
|
150
|
-
const whitespace = firstCodePoint(text, index);
|
|
151
|
-
if (whitespace === void 0 || !/\s/u.test(whitespace)) break;
|
|
152
|
-
index += whitespace.length;
|
|
153
|
-
if (protectsNextWord) breaks.add(index);
|
|
154
|
-
}
|
|
155
|
-
tokenStart = index;
|
|
156
|
-
}
|
|
157
|
-
return breaks;
|
|
158
|
-
};
|
|
159
139
|
const allowsBreak = (text, index, policy) => {
|
|
160
140
|
const previous = previousCodePoint(text, index);
|
|
161
141
|
const next = firstCodePoint(text, index);
|
|
@@ -165,7 +145,20 @@ const pushBreak = (breaks, text, index, policy) => {
|
|
|
165
145
|
if (index <= 0 || index > text.length || breaks.at(-1) === index) return;
|
|
166
146
|
if (allowsBreak(text, index, policy)) breaks.push(index);
|
|
167
147
|
};
|
|
148
|
+
const findSimpleGraphemeBreaks = (text) => {
|
|
149
|
+
if (!SIMPLE_BREAK_TEXT.test(text)) return;
|
|
150
|
+
const breaks = [];
|
|
151
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
152
|
+
const codeUnit = text.charCodeAt(index);
|
|
153
|
+
if (codeUnit >= 55296 && codeUnit <= 57343) return;
|
|
154
|
+
if (codeUnit === 13 && text.charCodeAt(index + 1) === 10) index += 1;
|
|
155
|
+
breaks.push(index + 1);
|
|
156
|
+
}
|
|
157
|
+
return breaks;
|
|
158
|
+
};
|
|
168
159
|
const findUnicodeGraphemeBreaks = (text, policy) => {
|
|
160
|
+
const simpleBreaks = findSimpleGraphemeBreaks(text);
|
|
161
|
+
if (simpleBreaks !== void 0) return simpleBreaks;
|
|
169
162
|
const breaks = [];
|
|
170
163
|
for (const segment of getSegmenter(graphemeSegmenters, "grapheme", policy?.locale).segment(text)) {
|
|
171
164
|
const end = segment.index + segment.segment.length;
|
|
@@ -173,13 +166,38 @@ const findUnicodeGraphemeBreaks = (text, policy) => {
|
|
|
173
166
|
}
|
|
174
167
|
return breaks;
|
|
175
168
|
};
|
|
169
|
+
const isSimpleWhitespace = (character, codePoint) => codePoint === 32 || codePoint >= 9 && codePoint <= 13 || codePoint > 127 && /\s/u.test(character);
|
|
170
|
+
/**
|
|
171
|
+
* Find the complete set of Unicode line-break opportunities for simple text
|
|
172
|
+
* without invoking `Intl.Segmenter`.
|
|
173
|
+
*
|
|
174
|
+
* Latin, Cyrillic, and Greek letters plus ordinary numbers, punctuation, and
|
|
175
|
+
* spacing are single grapheme clusters, except CRLF, which Unicode treats as
|
|
176
|
+
* one cluster. Returning `undefined` for combining marks, astral characters,
|
|
177
|
+
* dictionary-segmented scripts, CJK, and other complex input keeps the full
|
|
178
|
+
* Unicode provider responsible for those cases.
|
|
179
|
+
*/
|
|
180
|
+
const findSimpleBreaks = (text, policy) => {
|
|
181
|
+
if (!SIMPLE_BREAK_TEXT.test(text)) return;
|
|
182
|
+
const breaks = [];
|
|
183
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
184
|
+
const codePoint = text.charCodeAt(index);
|
|
185
|
+
if (codePoint >= 55296 && codePoint <= 57343) return;
|
|
186
|
+
if (codePoint === 13 && text.charCodeAt(index + 1) === 10) continue;
|
|
187
|
+
const character = text[index];
|
|
188
|
+
if (isSimpleWhitespace(character, codePoint) && !NONBREAKING_SPACES.has(character) || BREAK_AFTER_CHARACTER.has(character) || isLegacyEthiopicBreakCharacter(character, policy)) pushBreak(breaks, text, index + 1, policy);
|
|
189
|
+
}
|
|
190
|
+
return breaks;
|
|
191
|
+
};
|
|
176
192
|
const findUnicodeBreaks = (text, policy) => {
|
|
177
193
|
if (text.length === 0) return [];
|
|
194
|
+
const simpleBreaks = findSimpleBreaks(text, policy);
|
|
195
|
+
if (simpleBreaks !== void 0) return simpleBreaks;
|
|
178
196
|
const breaks = [];
|
|
179
197
|
const graphemeBreaks = findUnicodeGraphemeBreaks(text, policy);
|
|
180
198
|
for (const index of graphemeBreaks) {
|
|
181
199
|
const previous = previousCodePoint(text, index);
|
|
182
|
-
if (previous !== void 0 && (/\s/u.test(previous) || BREAK_AFTER_CHARACTER.has(previous) || isLegacyEthiopicBreakCharacter(previous, policy))) pushBreak(breaks, text, index, policy);
|
|
200
|
+
if (previous !== void 0 && (/\s/u.test(previous) && !NONBREAKING_SPACES.has(previous) || BREAK_AFTER_CHARACTER.has(previous) || isLegacyEthiopicBreakCharacter(previous, policy))) pushBreak(breaks, text, index, policy);
|
|
183
201
|
}
|
|
184
202
|
const wordSegments = [...getSegmenter(wordSegmenters, "word", policy?.locale).segment(text)];
|
|
185
203
|
for (const [segmentIndex, segment] of wordSegments.entries()) {
|
|
@@ -191,8 +209,7 @@ const findUnicodeBreaks = (text, policy) => {
|
|
|
191
209
|
const previous = previousCodePoint(text, index);
|
|
192
210
|
if (isCjkLineBreakParticipant(previous, policy)) pushBreak(breaks, text, index, policy);
|
|
193
211
|
}
|
|
194
|
-
|
|
195
|
-
return [...new Set(breaks)].filter((index) => !protectedBreaks.has(index)).sort((left, right) => left - right);
|
|
212
|
+
return [...new Set(breaks)].sort((left, right) => left - right);
|
|
196
213
|
};
|
|
197
214
|
const hyphenatorFor = (locale) => {
|
|
198
215
|
const normalized = locale?.trim().replaceAll("_", "-").toLowerCase();
|
|
@@ -226,6 +243,8 @@ const findPatternHyphenationBreaks = (text, policy) => {
|
|
|
226
243
|
return sourceOffset === text.length ? breaks : [];
|
|
227
244
|
};
|
|
228
245
|
const isDefaultHangingPunctuation = (text, policy) => {
|
|
246
|
+
const language = segmenterLocale(policy?.locale);
|
|
247
|
+
if (!language || !WORD_HANGING_PUNCTUATION_LANGUAGES.has(language)) return false;
|
|
229
248
|
const characters = [...text];
|
|
230
249
|
return characters.length > 0 && characters.every((character) => isProhibitedLineStart(character, policy));
|
|
231
250
|
};
|
|
@@ -2,7 +2,7 @@ import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWr
|
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
4
|
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
|
|
5
|
-
import {
|
|
5
|
+
import { layoutTextBoxContent } from "./textBoxParagraphLayout.js";
|
|
6
6
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
7
7
|
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
8
8
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
@@ -439,8 +439,11 @@ function measureBlock(block, contentWidth, floatingZones, cumulativeY, fieldValu
|
|
|
439
439
|
function measureTextBoxBlock(tb, fieldValues) {
|
|
440
440
|
const margins = tb.margins ?? DEFAULT_TEXTBOX_MARGINS;
|
|
441
441
|
const innerWidth = tb.width - margins.left - margins.right;
|
|
442
|
-
const innerMeasures = tb.content.map((
|
|
443
|
-
|
|
442
|
+
const innerMeasures = tb.content.map((block) => {
|
|
443
|
+
if (block.kind === "table") return measureTableBlock(block, innerWidth, fieldValues);
|
|
444
|
+
return measureParagraph(block, innerWidth, fieldValues ? { fieldValues } : void 0);
|
|
445
|
+
});
|
|
446
|
+
const contentBoxHeight = layoutTextBoxContent(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
|
|
444
447
|
const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
|
|
445
448
|
return {
|
|
446
449
|
kind: "textBox",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { setMeasureProvider } from "./measureProvider.js";
|
|
2
1
|
import { getFontKerningMode } from "./textMeasurementPolicy.js";
|
|
2
|
+
import { setMeasureProvider } from "./measureProvider.js";
|
|
3
3
|
import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
|
|
4
4
|
import { hasCjk, isCjkCodePoint, segmentByScript } from "../../utils/scriptSegments.js";
|
|
5
5
|
import { getCachedFontMetrics, getCachedTextWidth, getTextWidthCacheGeneration, setCachedFontMetrics, setCachedTextWidth } from "./cache.js";
|
|
@@ -34,7 +34,7 @@ declare function getResolvedData(fontFamily: string): ResolvedFontCache;
|
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
36
|
* buildFontString({ fontFamily: "Arial", fontSize: 12, bold: true })
|
|
37
|
-
* // Returns: "
|
|
37
|
+
* // Returns: "700 16px Arial, Arimo, Helvetica, sans-serif" (12pt = 16px)
|
|
38
38
|
*/
|
|
39
39
|
declare function buildFontString(style: FontStyle): string;
|
|
40
40
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveFontFamily } from "../../utils/fontResolver.js";
|
|
2
|
-
import "../../utils/fontWeights.js";
|
|
3
2
|
import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolicy.js";
|
|
3
|
+
import "../../utils/fontWeights.js";
|
|
4
4
|
//#region src/layout-engine/measure/measureHelpers.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure measurement helpers — no canvas, no DOM, no worker.
|
|
@@ -73,13 +73,13 @@ function getResolvedFallback(fontFamily) {
|
|
|
73
73
|
*
|
|
74
74
|
* @example
|
|
75
75
|
* buildFontString({ fontFamily: "Arial", fontSize: 12, bold: true })
|
|
76
|
-
* // Returns: "
|
|
76
|
+
* // Returns: "700 16px Arial, Arimo, Helvetica, sans-serif" (12pt = 16px)
|
|
77
77
|
*/
|
|
78
78
|
function buildFontString(style) {
|
|
79
79
|
const parts = [];
|
|
80
80
|
if (style.italic) parts.push("italic");
|
|
81
81
|
if (style.fontVariant) parts.push(style.fontVariant);
|
|
82
|
-
if (style.bold) parts.push("
|
|
82
|
+
if (style.bold) parts.push("700");
|
|
83
83
|
const fontSizePx = ptToPx(style.fontSize ?? 11);
|
|
84
84
|
parts.push(`${fontSizePx}px`);
|
|
85
85
|
const fontFamily = style.fontFamily ?? "Calibri";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
|
-
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
3
2
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
3
|
+
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
4
4
|
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
5
|
-
import "./lineBreakProvider.js";
|
|
5
|
+
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
6
6
|
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
7
7
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
8
8
|
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
@@ -27,7 +27,9 @@ const WIDTH_TOLERANCE = .5;
|
|
|
27
27
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
28
28
|
const JUSTIFY_SPACE_CONTRACTION_RATIO = .075;
|
|
29
29
|
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
30
|
-
const
|
|
30
|
+
const JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO = .022;
|
|
31
|
+
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .32;
|
|
32
|
+
const JUSTIFY_LIST_CONTINUATION_MAX_SHRINK_TOLERANCE_RATIO = .015;
|
|
31
33
|
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
32
34
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
33
35
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
@@ -137,7 +139,7 @@ const WORD_SINGLE_LINE_FLOOR = 1.15;
|
|
|
137
139
|
* `auto`/`atLeast` line rules; `exact` means exact (per OOXML §17.3.1.33)
|
|
138
140
|
* and stays untouched.
|
|
139
141
|
*/
|
|
140
|
-
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily) {
|
|
142
|
+
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
|
|
141
143
|
const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
|
|
142
144
|
fontSize,
|
|
143
145
|
fontFamily: fontFamily ?? DEFAULT_FONT_FAMILY
|
|
@@ -145,12 +147,22 @@ function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily) {
|
|
|
145
147
|
const lineRule = spacing?.lineRule ?? "auto";
|
|
146
148
|
if (lineRule === "auto" || lineRule === "atLeast") {
|
|
147
149
|
const floor = ptToPx(fontSize) * WORD_SINGLE_LINE_FLOOR;
|
|
148
|
-
if (result.lineHeight < floor) return {
|
|
150
|
+
if (result.lineHeight < floor) return applyDocumentGrid({
|
|
149
151
|
...result,
|
|
150
152
|
lineHeight: floor
|
|
151
|
-
};
|
|
153
|
+
}, attrs, spacing);
|
|
152
154
|
}
|
|
153
|
-
return result;
|
|
155
|
+
return applyDocumentGrid(result, attrs, spacing);
|
|
156
|
+
}
|
|
157
|
+
function applyDocumentGrid(typography, attrs, spacing) {
|
|
158
|
+
const pitch = attrs?.documentGridLinePitch;
|
|
159
|
+
if (pitch === void 0 || pitch <= 0 || attrs?.snapToGrid === false) return typography;
|
|
160
|
+
if (spacing?.lineRule === "exact") return typography;
|
|
161
|
+
const lineHeight = Math.ceil(typography.lineHeight / pitch - 1e-9) * pitch;
|
|
162
|
+
return lineHeight === typography.lineHeight ? typography : {
|
|
163
|
+
...typography,
|
|
164
|
+
lineHeight
|
|
165
|
+
};
|
|
154
166
|
}
|
|
155
167
|
/**
|
|
156
168
|
* Check if a run is a text run
|
|
@@ -344,6 +356,7 @@ function uppercaseLetterRatio(text) {
|
|
|
344
356
|
return letters === 0 ? 0 : uppercase / letters;
|
|
345
357
|
}
|
|
346
358
|
function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
359
|
+
if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "strict" };
|
|
347
360
|
if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
|
|
348
361
|
if (block.attrs?.listMarker !== void 0) {
|
|
349
362
|
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
@@ -352,7 +365,7 @@ function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
|
352
365
|
ratio: JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO
|
|
353
366
|
} : {
|
|
354
367
|
type: "width",
|
|
355
|
-
ratio:
|
|
368
|
+
ratio: JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO
|
|
356
369
|
};
|
|
357
370
|
const left = block.attrs.indent?.left ?? 0;
|
|
358
371
|
if (hanging > 0 && left > hanging) return {
|
|
@@ -361,7 +374,8 @@ function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
|
361
374
|
};
|
|
362
375
|
return {
|
|
363
376
|
type: "space",
|
|
364
|
-
ratio: JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO
|
|
377
|
+
ratio: JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO,
|
|
378
|
+
maxWidthRatio: JUSTIFY_LIST_CONTINUATION_MAX_SHRINK_TOLERANCE_RATIO
|
|
365
379
|
};
|
|
366
380
|
}
|
|
367
381
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
@@ -391,9 +405,12 @@ function compressibleSpaceWidth(text, style) {
|
|
|
391
405
|
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
392
406
|
}
|
|
393
407
|
function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
|
|
408
|
+
if (strategy.type === "strict") return 0;
|
|
394
409
|
if (strategy.type === "rounding") return WIDTH_TOLERANCE;
|
|
395
410
|
if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
|
|
396
|
-
|
|
411
|
+
const spaceTolerance = (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio;
|
|
412
|
+
const boundedTolerance = strategy.maxWidthRatio === void 0 ? spaceTolerance : Math.min(spaceTolerance, line.availableWidth * strategy.maxWidthRatio);
|
|
413
|
+
return Math.max(WIDTH_TOLERANCE, boundedTolerance);
|
|
397
414
|
}
|
|
398
415
|
function isShallowFullHangingListContinuation(block, isFirstLine) {
|
|
399
416
|
if (isFirstLine || block.attrs?.listMarker === void 0) return false;
|
|
@@ -410,8 +427,20 @@ function trimTrailingSpacesAndTabs(text) {
|
|
|
410
427
|
}
|
|
411
428
|
return text.slice(0, end);
|
|
412
429
|
}
|
|
430
|
+
function trailingCodePoint(text) {
|
|
431
|
+
if (text.length === 0) return;
|
|
432
|
+
const trailing = text.charCodeAt(text.length - 1);
|
|
433
|
+
const preceding = text.charCodeAt(text.length - 2);
|
|
434
|
+
const start = trailing >= 56320 && trailing <= 57343 && preceding >= 55296 && preceding <= 56319 ? text.length - 2 : text.length - 1;
|
|
435
|
+
return text.slice(start);
|
|
436
|
+
}
|
|
437
|
+
const usesDefaultHangingPunctuationClassifier = () => {
|
|
438
|
+
return (getLineBreakProvider().isHangingPunctuation ?? defaultLineBreakProvider.isHangingPunctuation) === defaultLineBreakProvider.isHangingPunctuation;
|
|
439
|
+
};
|
|
413
440
|
function trailingHangingPunctuationWidth(text, style, policy, enabled) {
|
|
414
441
|
if (!enabled || text.length === 0) return 0;
|
|
442
|
+
const trailing = trailingCodePoint(text);
|
|
443
|
+
if (usesDefaultHangingPunctuationClassifier() && !isHangingPunctuation(trailing ?? "", policy)) return 0;
|
|
415
444
|
const lastStart = findGraphemeBreaks(text, policy).at(-2) ?? 0;
|
|
416
445
|
const lastGrapheme = text.slice(lastStart);
|
|
417
446
|
if (!isHangingPunctuation(lastGrapheme, policy)) return 0;
|
|
@@ -448,13 +477,14 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
448
477
|
for (let index = 0; index < block.runs.length; index++) {
|
|
449
478
|
const run = block.runs[index];
|
|
450
479
|
if (!run || !isTextRun(run)) continue;
|
|
451
|
-
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
452
|
-
const token = trailing?.[1];
|
|
453
480
|
const policy = resolveEffectiveLineBreakPolicy({
|
|
454
481
|
attrs: block.attrs,
|
|
455
482
|
run
|
|
456
483
|
}).provider;
|
|
457
|
-
if (!
|
|
484
|
+
if (!policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
485
|
+
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
486
|
+
const token = trailing?.[1];
|
|
487
|
+
if (!token || token.length !== 1) continue;
|
|
458
488
|
let separator = trailing[2] ?? "";
|
|
459
489
|
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
460
490
|
let followingWord = "";
|
|
@@ -655,7 +685,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
655
685
|
totalHeight: 0
|
|
656
686
|
};
|
|
657
687
|
}
|
|
658
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY);
|
|
688
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY, attrs);
|
|
659
689
|
const outlineLineHeight = attrs?.reserveEmptyOutlineHeight ? emptyMetrics.lineHeight * 2 : emptyMetrics.lineHeight;
|
|
660
690
|
lines.push({
|
|
661
691
|
fromRun: 0,
|
|
@@ -675,9 +705,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
675
705
|
totalHeight
|
|
676
706
|
};
|
|
677
707
|
}
|
|
678
|
-
if (runs.length
|
|
679
|
-
const
|
|
680
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(run.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, run.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY);
|
|
708
|
+
if (runs.length > 0 && runs.every((run) => isTextRun(run) && isEmptyTextRun(run))) {
|
|
709
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY, attrs);
|
|
681
710
|
lines.push({
|
|
682
711
|
fromRun: 0,
|
|
683
712
|
fromChar: 0,
|
|
@@ -733,7 +762,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
733
762
|
finalTypography.lineHeight = objectHeight;
|
|
734
763
|
finalTypography.ascent = objectHeight;
|
|
735
764
|
finalTypography.descent = 0;
|
|
736
|
-
return finalTypography;
|
|
765
|
+
return applyDocumentGrid(finalTypography, attrs, spacing);
|
|
737
766
|
}
|
|
738
767
|
if (soleRun && isImageRun(soleRun)) {
|
|
739
768
|
finalTypography.lineHeight = objectHeight;
|
|
@@ -744,7 +773,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
744
773
|
finalTypography.ascent = objectHeight;
|
|
745
774
|
}
|
|
746
775
|
}
|
|
747
|
-
return finalTypography;
|
|
776
|
+
return applyDocumentGrid(finalTypography, attrs, spacing);
|
|
748
777
|
};
|
|
749
778
|
const getPostWrapAvailableWidth = () => {
|
|
750
779
|
if (!floatingZones || floatingZones.length === 0) return bodyContentWidth;
|
|
@@ -769,7 +798,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
769
798
|
toChar: currentLine.toChar,
|
|
770
799
|
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
771
800
|
...finalTypography,
|
|
772
|
-
...currentLine.discretionaryHyphen ? { discretionaryHyphen: currentLine.discretionaryHyphen } : {}
|
|
801
|
+
...currentLine.discretionaryHyphen ? { discretionaryHyphen: currentLine.discretionaryHyphen } : {},
|
|
802
|
+
...currentLine.renderedPageBreakBefore ? { renderedPageBreakBefore: true } : {}
|
|
773
803
|
};
|
|
774
804
|
if (currentLine.leftOffset > 0) line.leftOffset = currentLine.leftOffset;
|
|
775
805
|
if (currentLine.rightOffset > 0) line.rightOffset = currentLine.rightOffset;
|
|
@@ -827,6 +857,22 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
827
857
|
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
828
858
|
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
829
859
|
const run = runs[runIndex];
|
|
860
|
+
if (run.kind === "renderedPageBreak") {
|
|
861
|
+
if (!runs.slice(runIndex + 1).some((followingRun) => followingRun.kind !== "renderedPageBreak")) {
|
|
862
|
+
currentLine.toRun = runIndex;
|
|
863
|
+
currentLine.toChar = 0;
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (currentLine.width > 0 || currentLine.maxImageHeightPx > 0 || currentLine.maxMathHeightPx > 0) startNewLine(runIndex + 1, 0);
|
|
867
|
+
else {
|
|
868
|
+
currentLine.fromRun = runIndex + 1;
|
|
869
|
+
currentLine.toRun = runIndex + 1;
|
|
870
|
+
currentLine.fromChar = 0;
|
|
871
|
+
currentLine.toChar = 0;
|
|
872
|
+
}
|
|
873
|
+
currentLine.renderedPageBreakBefore = true;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
830
876
|
if (isLineBreakRun(run)) {
|
|
831
877
|
currentLine.toRun = runIndex;
|
|
832
878
|
currentLine.toChar = 0;
|
|
@@ -952,13 +998,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
952
998
|
const wordBreaks = findWordBreaks(text, breakPolicy);
|
|
953
999
|
let charIndex = crossRunResume?.runIndex === runIndex ? crossRunResume.charIndex : 0;
|
|
954
1000
|
if (crossRunResume?.runIndex === runIndex) crossRunResume = void 0;
|
|
1001
|
+
let wordBreakIndex = 0;
|
|
955
1002
|
let activeHyphenationWord;
|
|
956
1003
|
while (charIndex < text.length) {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
nextBreak = breakPoint;
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
1004
|
+
while ((wordBreaks[wordBreakIndex] ?? Number.POSITIVE_INFINITY) <= charIndex) wordBreakIndex += 1;
|
|
1005
|
+
const nextBreak = wordBreaks[wordBreakIndex] ?? text.length;
|
|
962
1006
|
const word = text.slice(charIndex, nextBreak);
|
|
963
1007
|
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
964
1008
|
const wordWidth = measureTextWidth(measuredWord, style);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toPaintedText } from "./textMeasurementPolicy.js";
|
|
1
2
|
import { panic } from "better-result";
|
|
2
3
|
//#region src/layout-engine/measure/measureProvider.ts
|
|
3
4
|
/**
|
|
@@ -28,8 +29,8 @@ const resetMeasureProvider = () => {
|
|
|
28
29
|
activeMeasureProvider = throwingMeasureProvider;
|
|
29
30
|
};
|
|
30
31
|
const getFontMetrics = (style) => activeMeasureProvider.getFontMetrics(style);
|
|
31
|
-
const measureTextWidth = (text, style) => activeMeasureProvider.measureTextWidth(text, style);
|
|
32
|
-
const measureText = (text, style) => activeMeasureProvider.measureText(text, style);
|
|
33
|
-
const measureRun = (text, style) => activeMeasureProvider.measureRun(text, style);
|
|
32
|
+
const measureTextWidth = (text, style) => activeMeasureProvider.measureTextWidth(toPaintedText(text), style);
|
|
33
|
+
const measureText = (text, style) => activeMeasureProvider.measureText(toPaintedText(text), style);
|
|
34
|
+
const measureRun = (text, style) => activeMeasureProvider.measureRun(toPaintedText(text), style);
|
|
34
35
|
//#endregion
|
|
35
36
|
export { getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
2
2
|
import { isFloatingTextBoxBlock } from "../types.js";
|
|
3
|
-
import { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
|
|
3
|
+
import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
|
|
4
4
|
//#region src/layout-engine/measure/tableCellFlow.ts
|
|
5
5
|
const createTableCellFlowState = () => ({
|
|
6
6
|
height: 0,
|
|
@@ -57,7 +57,10 @@ const placeTableCellBlock = (state, block, measure) => {
|
|
|
57
57
|
}
|
|
58
58
|
const spacing = paragraphSpacing(block, measure);
|
|
59
59
|
const empty = isEmptyParagraph(block);
|
|
60
|
-
const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing :
|
|
60
|
+
const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing : collapseParagraphSpacing({
|
|
61
|
+
before: spacing.before,
|
|
62
|
+
after: state.trailingSpacing
|
|
63
|
+
});
|
|
61
64
|
const contentTop = top + leadingSpacing;
|
|
62
65
|
state.height = contentTop + contentHeight;
|
|
63
66
|
state.previousParagraphWasEmpty = empty;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { ParagraphBlock, ParagraphMeasure } from "../types.js";
|
|
1
|
+
import { ParagraphBlock, ParagraphMeasure, TableBlock, TableMeasure } from "../types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/measure/textBoxParagraphLayout.d.ts
|
|
4
|
-
type
|
|
4
|
+
type TextBoxContentPlacement = {
|
|
5
5
|
leadingSpacing: number;
|
|
6
6
|
contentHeight: number;
|
|
7
7
|
};
|
|
8
|
-
type
|
|
9
|
-
placements:
|
|
8
|
+
type TextBoxContentLayout = {
|
|
9
|
+
placements: TextBoxContentPlacement[];
|
|
10
10
|
totalHeight: number;
|
|
11
11
|
};
|
|
12
|
+
type TextBoxContentBlock = ParagraphBlock | TableBlock;
|
|
13
|
+
type TextBoxContentMeasure = ParagraphMeasure | TableMeasure;
|
|
12
14
|
/**
|
|
13
|
-
* Lay out a text box's
|
|
15
|
+
* Lay out a text box's block story as one unpaginated flow. Adjacent paragraph
|
|
14
16
|
* before/after spacing collapses to the larger value, matching body flow.
|
|
15
17
|
*/
|
|
16
|
-
declare function
|
|
18
|
+
declare function layoutTextBoxContent(blocks: readonly TextBoxContentBlock[], measures: readonly TextBoxContentMeasure[]): TextBoxContentLayout;
|
|
17
19
|
//#endregion
|
|
18
|
-
export {
|
|
20
|
+
export { TextBoxContentLayout, TextBoxContentPlacement, layoutTextBoxContent };
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
2
|
-
import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
|
|
2
|
+
import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
|
|
3
3
|
import { panic } from "better-result";
|
|
4
4
|
//#region src/layout-engine/measure/textBoxParagraphLayout.ts
|
|
5
5
|
/**
|
|
6
|
-
* Lay out a text box's
|
|
6
|
+
* Lay out a text box's block story as one unpaginated flow. Adjacent paragraph
|
|
7
7
|
* before/after spacing collapses to the larger value, matching body flow.
|
|
8
8
|
*/
|
|
9
|
-
function
|
|
10
|
-
if (blocks.length !== measures.length) panic("
|
|
9
|
+
function layoutTextBoxContent(blocks, measures) {
|
|
10
|
+
if (blocks.length !== measures.length) panic("layoutTextBoxContent: block and measure counts must match");
|
|
11
11
|
const placements = [];
|
|
12
12
|
let totalHeight = 0;
|
|
13
13
|
let trailingSpacing = 0;
|
|
14
14
|
for (let index = 0; index < blocks.length; index += 1) {
|
|
15
15
|
const block = blocks[index];
|
|
16
16
|
const measure = measures[index];
|
|
17
|
-
|
|
17
|
+
if (block.kind === "table") {
|
|
18
|
+
if (measure.kind !== "table") panic("layoutTextBoxContent: block and measure kinds must match");
|
|
19
|
+
const leadingSpacing = trailingSpacing;
|
|
20
|
+
const contentHeight = measure.totalHeight;
|
|
21
|
+
placements.push({
|
|
22
|
+
leadingSpacing,
|
|
23
|
+
contentHeight
|
|
24
|
+
});
|
|
25
|
+
totalHeight += leadingSpacing + contentHeight;
|
|
26
|
+
trailingSpacing = 0;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (measure.kind !== "paragraph") panic("layoutTextBoxContent: block and measure kinds must match");
|
|
30
|
+
const leadingSpacing = collapseParagraphSpacing({
|
|
31
|
+
before: getParagraphSpacingBefore(block),
|
|
32
|
+
after: trailingSpacing
|
|
33
|
+
});
|
|
18
34
|
const contentHeight = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
|
|
19
35
|
placements.push({
|
|
20
36
|
leadingSpacing,
|
|
@@ -29,4 +45,4 @@ function layoutTextBoxParagraphs(blocks, measures) {
|
|
|
29
45
|
};
|
|
30
46
|
}
|
|
31
47
|
//#endregion
|
|
32
|
-
export {
|
|
48
|
+
export { layoutTextBoxContent };
|
|
@@ -14,5 +14,7 @@ declare function getRunFontKerningMode(run: RunKerningInput, fallbackFontSize: n
|
|
|
14
14
|
declare function getFontKerningMode(style: Pick<FontStyle, "kerning">): FontKerningMode;
|
|
15
15
|
/** Count ordinary ASCII spaces that justification may compress. */
|
|
16
16
|
declare function countCompressibleSpaces(text: string): number;
|
|
17
|
+
/** Paint OOXML no-break hyphens with the ordinary hyphen glyph. */
|
|
18
|
+
declare function toPaintedText(text: string): string;
|
|
17
19
|
//#endregion
|
|
18
|
-
export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
|
|
20
|
+
export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText };
|
|
@@ -3,6 +3,8 @@ const FONT_KERNING_MODE = {
|
|
|
3
3
|
enabled: "normal",
|
|
4
4
|
disabled: "none"
|
|
5
5
|
};
|
|
6
|
+
const NO_BREAK_HYPHEN = "‑";
|
|
7
|
+
const PAINTED_HYPHEN = "-";
|
|
6
8
|
/** Resolve an authored kerning threshold against the effective run size. */
|
|
7
9
|
function getRunFontKerningMode(run, fallbackFontSize) {
|
|
8
10
|
const threshold = run.kerningMinPt;
|
|
@@ -20,5 +22,9 @@ function countCompressibleSpaces(text) {
|
|
|
20
22
|
for (const char of text) if (char === " ") count += 1;
|
|
21
23
|
return count;
|
|
22
24
|
}
|
|
25
|
+
/** Paint OOXML no-break hyphens with the ordinary hyphen glyph. */
|
|
26
|
+
function toPaintedText(text) {
|
|
27
|
+
return text.replaceAll(NO_BREAK_HYPHEN, PAINTED_HYPHEN);
|
|
28
|
+
}
|
|
23
29
|
//#endregion
|
|
24
|
-
export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
|
|
30
|
+
export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FOOTNOTE_SEPARATOR_HEIGHT } from "./types.js";
|
|
2
|
+
import { collapseParagraphSpacing } from "./paragraphSpacing.js";
|
|
2
3
|
import { panic } from "better-result";
|
|
3
4
|
//#region src/layout-engine/paginator.ts
|
|
4
5
|
/**
|
|
@@ -178,7 +179,10 @@ function createPaginator(options) {
|
|
|
178
179
|
function addFragment(fragment, height, spaceBefore = 0, spaceAfter = 0) {
|
|
179
180
|
const initialState = getCurrentState();
|
|
180
181
|
const initialColumnIndex = initialState.columnIndex;
|
|
181
|
-
const effectiveSpaceBefore =
|
|
182
|
+
const effectiveSpaceBefore = collapseParagraphSpacing({
|
|
183
|
+
before: spaceBefore,
|
|
184
|
+
after: initialState.trailingSpacing
|
|
185
|
+
});
|
|
182
186
|
const state = ensureFits(effectiveSpaceBefore + height);
|
|
183
187
|
const actualSpaceBefore = state === initialState && state.columnIndex === initialColumnIndex ? effectiveSpaceBefore : spaceBefore;
|
|
184
188
|
const x = getColumnX(state.columnIndex);
|
|
@@ -1,17 +1,41 @@
|
|
|
1
|
-
import { ParagraphBlock } from "./types.js";
|
|
1
|
+
import { FlowBlock, ParagraphBlock } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/paragraphSpacing.d.ts
|
|
4
|
+
type EffectiveParagraphSpacing = Readonly<{
|
|
5
|
+
before: number;
|
|
6
|
+
after: number;
|
|
7
|
+
}>;
|
|
8
|
+
type CollapseParagraphSpacingOptions = {
|
|
9
|
+
before: number;
|
|
10
|
+
after: number;
|
|
11
|
+
};
|
|
12
|
+
/** Collapse adjacent paragraph spacing to the larger authored side. */
|
|
13
|
+
declare const collapseParagraphSpacing: ({
|
|
14
|
+
before,
|
|
15
|
+
after
|
|
16
|
+
}: CollapseParagraphSpacingOptions) => number;
|
|
4
17
|
/** Whether a paragraph has no visible run content. */
|
|
5
18
|
declare function isEmptyParagraph(block: ParagraphBlock): boolean;
|
|
6
19
|
/**
|
|
7
20
|
* Resolve leading paragraph spacing after applying the empty-paragraph
|
|
8
21
|
* inherited-spacing collapse rule.
|
|
9
22
|
*/
|
|
10
|
-
declare
|
|
23
|
+
declare const getParagraphSpacingBefore: (block: ParagraphBlock) => number;
|
|
11
24
|
/**
|
|
12
25
|
* Resolve trailing paragraph spacing after applying the empty-paragraph
|
|
13
26
|
* inherited-spacing collapse rule.
|
|
14
27
|
*/
|
|
15
|
-
declare
|
|
28
|
+
declare const getParagraphSpacingAfter: (block: ParagraphBlock) => number;
|
|
29
|
+
/** Resolve empty-paragraph spacing without modifying the authored block. */
|
|
30
|
+
declare const resolveEffectiveParagraphSpacing: (block: ParagraphBlock) => EffectiveParagraphSpacing;
|
|
31
|
+
/**
|
|
32
|
+
* Derive sequence-level spacing suppression with structural sharing.
|
|
33
|
+
*
|
|
34
|
+
* Contextual and automatic list spacing affect layout values, not authored
|
|
35
|
+
* OOXML formatting. Only changed branches are cloned; the input tree remains
|
|
36
|
+
* untouched and repeated resolution is idempotent.
|
|
37
|
+
*/
|
|
38
|
+
declare const resolveEffectiveParagraphSpacingTree: (blocks: FlowBlock[]) => FlowBlock[];
|
|
39
|
+
declare const paragraphsShareStyle: (previous: ParagraphBlock, current: ParagraphBlock) => boolean;
|
|
16
40
|
//#endregion
|
|
17
|
-
export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
|
|
41
|
+
export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
|