@stll/folio-core 0.32.2 → 0.33.1
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/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
2
|
+
import { SCRIPT_CLASS, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
|
|
3
|
+
import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
|
|
4
|
+
//#region src/layout-engine/measure/advanceComposition.ts
|
|
5
|
+
/**
|
|
6
|
+
* The arithmetic that turns raw glyph advances into measured widths.
|
|
7
|
+
*
|
|
8
|
+
* Text transform, per-script font selection, letter spacing and horizontal
|
|
9
|
+
* scale are the same calculation whoever supplies the advances: a canvas, a
|
|
10
|
+
* parsed font, a shaper. Only the advance source differs. Keeping the
|
|
11
|
+
* calculation here and parameterizing the source means a second measurement
|
|
12
|
+
* backend can disagree with the canvas one about a glyph's width (which the
|
|
13
|
+
* differential tests measure) but cannot disagree about where letter spacing
|
|
14
|
+
* goes or which font a CJK code point takes.
|
|
15
|
+
*
|
|
16
|
+
* Pure: no canvas, no DOM, no caching.
|
|
17
|
+
*/
|
|
18
|
+
const applyMeasurementTextTransform = (text, style) => style.textTransform === "uppercase" ? text.toLocaleUpperCase() : text;
|
|
19
|
+
const countCodePoints = (text) => [...text].length;
|
|
20
|
+
/** Resolve the independent font/style slot a script segment selects. */
|
|
21
|
+
const scriptStyle = (style, script) => {
|
|
22
|
+
if (script === SCRIPT_CLASS.eastAsia) {
|
|
23
|
+
if (style.eastAsiaFontFamily === void 0) return style;
|
|
24
|
+
const result = {
|
|
25
|
+
...style,
|
|
26
|
+
fontFamily: style.eastAsiaFontFamily
|
|
27
|
+
};
|
|
28
|
+
delete result.alternateFontFamily;
|
|
29
|
+
if (style.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = style.eastAsiaAlternateFontFamily;
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
if (script === SCRIPT_CLASS.complex) return applyComplexScriptFormatting(style, style);
|
|
33
|
+
return style;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Keep only the properties that change a glyph's advance, so a per-script
|
|
37
|
+
* segment takes the single-font path: letter spacing and horizontal scale are
|
|
38
|
+
* applied once over the whole string, never per segment.
|
|
39
|
+
*/
|
|
40
|
+
const glyphAdvanceStyle = (style) => {
|
|
41
|
+
const result = {};
|
|
42
|
+
if (style.fontFamily !== void 0) result.fontFamily = style.fontFamily;
|
|
43
|
+
if (style.alternateFontFamily !== void 0) result.alternateFontFamily = style.alternateFontFamily;
|
|
44
|
+
if (style.fontSize !== void 0) result.fontSize = style.fontSize;
|
|
45
|
+
if (style.bold !== void 0) result.bold = style.bold;
|
|
46
|
+
if (style.italic !== void 0) result.italic = style.italic;
|
|
47
|
+
if (style.fontVariant !== void 0) result.fontVariant = style.fontVariant;
|
|
48
|
+
if (style.kerning !== void 0) result.kerning = style.kerning;
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
const needsPerScriptFonts = (style, text) => style.eastAsiaFontFamily !== void 0 && [...text].some((c) => isEastAsia(c)) || hasComplexScriptFormatting(style) && hasComplexScript(text);
|
|
52
|
+
const isEastAsia = (char) => {
|
|
53
|
+
return scriptClassOf(char.codePointAt(0)) === SCRIPT_CLASS.eastAsia;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Width of a string: the advance width used for line breaking, never the
|
|
57
|
+
* painted (ink) extent, which includes overhang a break decision must ignore.
|
|
58
|
+
*/
|
|
59
|
+
const composeTextWidth = ({ text, style: source, advanceOf }) => {
|
|
60
|
+
if (!text) return 0;
|
|
61
|
+
const style = source.forceComplexScript ? applyComplexScriptFormatting(source, source) : source;
|
|
62
|
+
const transformed = applyMeasurementTextTransform(text, style);
|
|
63
|
+
const letterSpacing = style.letterSpacing ?? 0;
|
|
64
|
+
const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
|
|
65
|
+
const glyphWidth = !source.forceComplexScript && !letterSpacing && needsPerScriptFonts(style, transformed) ? segmentByScript(transformed).reduce((sum, segment) => sum + advanceOf(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script))), 0) : advanceOf(transformed, style);
|
|
66
|
+
const codePoints = countCodePoints(transformed);
|
|
67
|
+
return (letterSpacing && codePoints > 1 ? glyphWidth + letterSpacing * (codePoints - 1) : glyphWidth) * horizontalScale;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Per-code-point advances for click positioning.
|
|
71
|
+
*
|
|
72
|
+
* Iterates whole code points so an astral ideograph takes the East-Asian font
|
|
73
|
+
* and gets a real width, while `charWidths` stays one entry per UTF-16 unit
|
|
74
|
+
* (the trailing unit of a surrogate pair carries 0) so it keeps aligning with
|
|
75
|
+
* ProseMirror offsets. The total differs from {@link composeTextWidth} for a
|
|
76
|
+
* kerned or ligature-forming run, because each code point is measured alone.
|
|
77
|
+
*/
|
|
78
|
+
const composeRunMeasurement = ({ text, style: source, metrics, advanceOf, advancesOf }) => {
|
|
79
|
+
const style = source.forceComplexScript ? applyComplexScriptFormatting(source, source) : source;
|
|
80
|
+
if (!text) return {
|
|
81
|
+
width: 0,
|
|
82
|
+
charWidths: [],
|
|
83
|
+
metrics
|
|
84
|
+
};
|
|
85
|
+
const perScript = !style.letterSpacing;
|
|
86
|
+
const letterSpacing = style.letterSpacing ?? 0;
|
|
87
|
+
const scale = getHorizontalScaleFactor(style.horizontalScale);
|
|
88
|
+
const charWidths = [];
|
|
89
|
+
let totalWidth = 0;
|
|
90
|
+
let offset = 0;
|
|
91
|
+
const shaped = shapedAdvances({
|
|
92
|
+
text,
|
|
93
|
+
style,
|
|
94
|
+
advancesOf,
|
|
95
|
+
perScript
|
|
96
|
+
});
|
|
97
|
+
let codePointIndex = 0;
|
|
98
|
+
for (const char of text) {
|
|
99
|
+
const cp = char.codePointAt(0);
|
|
100
|
+
const charStyle = perScript ? scriptStyle(style, scriptClassOf(cp)) : style;
|
|
101
|
+
let charWidth = shaped.get(codePointIndex) ?? advanceOf(applyMeasurementTextTransform(char, style), charStyle);
|
|
102
|
+
if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
|
|
103
|
+
charWidth *= scale;
|
|
104
|
+
charWidths.push(charWidth);
|
|
105
|
+
if (char.length === 2) charWidths.push(0);
|
|
106
|
+
totalWidth += charWidth;
|
|
107
|
+
offset += char.length;
|
|
108
|
+
codePointIndex += 1;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
width: totalWidth,
|
|
112
|
+
charWidths,
|
|
113
|
+
metrics
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Advances by code-point index for every stretch of the run that shapes.
|
|
118
|
+
*
|
|
119
|
+
* Each such stretch is measured in one call, because that is the only unit
|
|
120
|
+
* shaping has an answer for. A text transform that changes the length of a
|
|
121
|
+
* shaping stretch would break the alignment between the advances and the
|
|
122
|
+
* source's code points, so the transform is applied to the stretch and the
|
|
123
|
+
* result is only used when it did not change.
|
|
124
|
+
*/
|
|
125
|
+
const shapedAdvances = ({ text, style, advancesOf, perScript }) => {
|
|
126
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
127
|
+
if (advancesOf === void 0 || !hasComplexScript(text)) return byIndex;
|
|
128
|
+
const characters = [...text];
|
|
129
|
+
const shapingStyle = perScript ? scriptStyle(style, SCRIPT_CLASS.complex) : style;
|
|
130
|
+
let start = 0;
|
|
131
|
+
while (start < characters.length) {
|
|
132
|
+
if (scriptClassOf(characters[start].codePointAt(0)) !== SCRIPT_CLASS.complex) {
|
|
133
|
+
start += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
let end = start + 1;
|
|
137
|
+
while (end < characters.length && scriptClassOf(characters[end].codePointAt(0)) === SCRIPT_CLASS.complex) end += 1;
|
|
138
|
+
const segment = characters.slice(start, end).join("");
|
|
139
|
+
if (applyMeasurementTextTransform(segment, style) === segment) {
|
|
140
|
+
const advances = advancesOf(segment, shapingStyle);
|
|
141
|
+
for (const [offset, advance] of advances.entries()) byIndex.set(start + offset, advance);
|
|
142
|
+
}
|
|
143
|
+
start = end;
|
|
144
|
+
}
|
|
145
|
+
return byIndex;
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
export { applyMeasurementTextTransform, composeRunMeasurement, composeTextWidth, glyphAdvanceStyle, scriptStyle };
|
|
@@ -81,6 +81,8 @@ function canvasGetFontMetrics(style) {
|
|
|
81
81
|
fontSize,
|
|
82
82
|
ascent: cached.ascent,
|
|
83
83
|
descent: cached.descent,
|
|
84
|
+
fontBoxAscent: cached.fontBoxAscent,
|
|
85
|
+
fontBoxDescent: cached.fontBoxDescent,
|
|
84
86
|
lineHeight: cached.lineHeight,
|
|
85
87
|
fontFamily,
|
|
86
88
|
singleLineRatio: cached.singleLineRatio
|
|
@@ -88,6 +90,8 @@ function canvasGetFontMetrics(style) {
|
|
|
88
90
|
const fontSizePx = ptToPx(fontSize);
|
|
89
91
|
let ascent = fontSizePx * DEFAULT_ASCENT_RATIO;
|
|
90
92
|
let descent = fontSizePx * DEFAULT_DESCENT_RATIO;
|
|
93
|
+
let fontBoxAscent = ascent;
|
|
94
|
+
let fontBoxDescent = descent;
|
|
91
95
|
let lineHeight = fontSizePx * DEFAULT_LINE_HEIGHT_MULTIPLIER;
|
|
92
96
|
try {
|
|
93
97
|
const ctx = getCanvasContext();
|
|
@@ -98,6 +102,13 @@ function canvasGetFontMetrics(style) {
|
|
|
98
102
|
ascent = metrics.actualBoundingBoxAscent;
|
|
99
103
|
descent = metrics.actualBoundingBoxDescent;
|
|
100
104
|
}
|
|
105
|
+
if (typeof metrics.fontBoundingBoxAscent === "number" && typeof metrics.fontBoundingBoxDescent === "number") {
|
|
106
|
+
fontBoxAscent = metrics.fontBoundingBoxAscent;
|
|
107
|
+
fontBoxDescent = metrics.fontBoundingBoxDescent;
|
|
108
|
+
} else {
|
|
109
|
+
fontBoxAscent = ascent;
|
|
110
|
+
fontBoxDescent = descent;
|
|
111
|
+
}
|
|
101
112
|
} catch {}
|
|
102
113
|
lineHeight = Math.max(lineHeight, ascent + descent);
|
|
103
114
|
const singleLineRatio = getResolvedData(fontFamily, alternateFontFamily).singleLineRatio;
|
|
@@ -105,6 +116,8 @@ function canvasGetFontMetrics(style) {
|
|
|
105
116
|
fontSize,
|
|
106
117
|
ascent,
|
|
107
118
|
descent,
|
|
119
|
+
fontBoxAscent,
|
|
120
|
+
fontBoxDescent,
|
|
108
121
|
lineHeight,
|
|
109
122
|
fontFamily,
|
|
110
123
|
singleLineRatio
|
|
@@ -369,7 +369,7 @@ function uppercaseLetterRatio(text) {
|
|
|
369
369
|
return letters === 0 ? 0 : uppercase / letters;
|
|
370
370
|
}
|
|
371
371
|
function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
372
|
-
if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "
|
|
372
|
+
if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "rounding" };
|
|
373
373
|
if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
|
|
374
374
|
if (block.attrs?.listMarker !== void 0) {
|
|
375
375
|
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
@@ -461,7 +461,6 @@ function compressibleSpaceWidth(text, style) {
|
|
|
461
461
|
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
462
462
|
}
|
|
463
463
|
function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
|
|
464
|
-
if (strategy.type === "strict") return 0;
|
|
465
464
|
if (strategy.type === "rounding") return WIDTH_TOLERANCE;
|
|
466
465
|
if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
|
|
467
466
|
const spaceTolerance = (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio;
|
|
@@ -48,8 +48,20 @@ type FontStyle = {
|
|
|
48
48
|
*/
|
|
49
49
|
type FontMetrics = {
|
|
50
50
|
fontSize: number;
|
|
51
|
+
/** Ink extent above the baseline: how far the glyphs actually reach. */
|
|
51
52
|
ascent: number;
|
|
53
|
+
/** Ink extent below the baseline. */
|
|
52
54
|
descent: number;
|
|
55
|
+
/**
|
|
56
|
+
* Extent of the font's own box (hhea/OS-2), which is wider than the ink and
|
|
57
|
+
* is what a browser builds an inline content area from. A painter that
|
|
58
|
+
* positions a text box from the ink extents instead puts every baseline out
|
|
59
|
+
* by half the difference between the two, per face, with nothing to
|
|
60
|
+
* attribute it to. Carried beside the ink extents rather than replacing them
|
|
61
|
+
* because line boxes are sized from the ink.
|
|
62
|
+
*/
|
|
63
|
+
fontBoxAscent: number;
|
|
64
|
+
fontBoxDescent: number;
|
|
53
65
|
lineHeight: number;
|
|
54
66
|
fontFamily: string;
|
|
55
67
|
/** OS/2 single-line ratio for OOXML line spacing calculation */
|
|
@@ -6,10 +6,11 @@ type TableInlinePlacement = {
|
|
|
6
6
|
alignment: "left" | "right";
|
|
7
7
|
offset: number;
|
|
8
8
|
};
|
|
9
|
+
type LeadingEdgeTable = Pick<TableBlock, "bidi" | "indent" | "indentCompatibility" | "justification" | "rows">;
|
|
9
10
|
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
10
|
-
declare const resolveTableInlinePlacement: (table:
|
|
11
|
+
declare const resolveTableInlinePlacement: (table: LeadingEdgeTable, rowJustification?: TableBlock["justification"]) => TableInlinePlacement;
|
|
11
12
|
type ResolveTableInlineOffsetOptions = {
|
|
12
|
-
table:
|
|
13
|
+
table: LeadingEdgeTable;
|
|
13
14
|
rowJustification?: TableBlock["justification"];
|
|
14
15
|
frameWidth: number;
|
|
15
16
|
tableWidth: number;
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import { resolveTableCellPadding } from "../types.js";
|
|
1
2
|
//#region src/layout-engine/measure/tableInlinePlacement.ts
|
|
3
|
+
/**
|
|
4
|
+
* Distance the leading border is pulled back so the leading cell's text lands
|
|
5
|
+
* on `w:tblInd`. Zero unless the document uses the pre-Word-2013 indent
|
|
6
|
+
* semantics, where the indent measures to that text edge rather than to the
|
|
7
|
+
* border. Only the leading, indent-bearing edge compensates; centered and
|
|
8
|
+
* trailing-edge tables are placed by their border box.
|
|
9
|
+
*/
|
|
10
|
+
const leadingEdgeCompensation = (table) => {
|
|
11
|
+
if (table.indentCompatibility?.type !== "legacy") return 0;
|
|
12
|
+
const padding = resolveTableCellPadding(table.rows.at(0)?.cells.at(0));
|
|
13
|
+
return table.bidi === true ? padding.right : padding.left;
|
|
14
|
+
};
|
|
2
15
|
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
3
16
|
const resolveTableInlinePlacement = (table, rowJustification) => {
|
|
4
17
|
const logicalJustification = rowJustification ?? table.justification ?? "left";
|
|
5
18
|
if (logicalJustification === "center") return { alignment: "center" };
|
|
6
|
-
const offset = logicalJustification === "left" ? table.indent ?? 0 : 0;
|
|
19
|
+
const offset = logicalJustification === "left" ? (table.indent ?? 0) - leadingEdgeCompensation(table) : 0;
|
|
7
20
|
if (table.bidi !== true) return {
|
|
8
21
|
alignment: logicalJustification,
|
|
9
22
|
offset
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { TableBlock } from "./types.js";
|
|
2
|
+
//#region src/layout-engine/tableIndentCompatibility.d.ts
|
|
3
|
+
declare const resolveTableIndentCompatibility: (compatibilityMode: number | undefined) => NonNullable<TableBlock["indentCompatibility"]> | undefined;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { resolveTableIndentCompatibility };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/layout-engine/tableIndentCompatibility.ts
|
|
2
|
+
/**
|
|
3
|
+
* First `compatibilityMode` that measures `w:tblInd` to the table border.
|
|
4
|
+
*
|
|
5
|
+
* Word 2013 moved the reference edge: from that mode on, `w:tblInd` offsets
|
|
6
|
+
* the border and the leading cell margin pushes the text further in. Earlier
|
|
7
|
+
* modes measure the indent to the leading cell's text edge and pull the border
|
|
8
|
+
* back by that cell's leading margin, so the first character lands exactly on
|
|
9
|
+
* the indent. A document that declares no `compatSetting` at all is read with
|
|
10
|
+
* the oldest semantics, so it takes the legacy rule too.
|
|
11
|
+
*/
|
|
12
|
+
const FIRST_BORDER_EDGE_INDENT_COMPATIBILITY_MODE = 15;
|
|
13
|
+
const resolveTableIndentCompatibility = (compatibilityMode) => compatibilityMode !== void 0 && compatibilityMode >= FIRST_BORDER_EDGE_INDENT_COMPATIBILITY_MODE ? void 0 : { type: "legacy" };
|
|
14
|
+
//#endregion
|
|
15
|
+
export { resolveTableIndentCompatibility };
|
|
@@ -644,6 +644,14 @@ type TableBlock = {
|
|
|
644
644
|
justification?: "left" | "center" | "right";
|
|
645
645
|
/** Table indent from the leading margin (in pixels, from w:tblInd). */
|
|
646
646
|
indent?: number;
|
|
647
|
+
/**
|
|
648
|
+
* Pre-Word-2013 `w:tblInd` semantics: the indent measures to the leading
|
|
649
|
+
* cell's text edge instead of the table border. Derived from the document's
|
|
650
|
+
* `compatibilityMode` (see `resolveTableIndentCompatibility`).
|
|
651
|
+
*/
|
|
652
|
+
indentCompatibility?: {
|
|
653
|
+
type: "legacy";
|
|
654
|
+
};
|
|
647
655
|
/** Right-to-left column order (w:bidiVisual): logical column 0 paints on the right. */
|
|
648
656
|
bidi?: boolean;
|
|
649
657
|
/** Floating table properties (pixel values). */
|
|
@@ -84,6 +84,12 @@ type FootnoteRenderItem = {
|
|
|
84
84
|
/**
|
|
85
85
|
* Options for rendering a page
|
|
86
86
|
*/
|
|
87
|
+
/** Everything a replacement page renderer is handed. */
|
|
88
|
+
type PaintPageRequest = {
|
|
89
|
+
readonly page: Page;
|
|
90
|
+
readonly context: RenderContext;
|
|
91
|
+
readonly options: RenderPageOptions;
|
|
92
|
+
};
|
|
87
93
|
type RenderPageOptions = {
|
|
88
94
|
/** Document to create elements in (default: window.document) */
|
|
89
95
|
document?: Document;
|
|
@@ -163,6 +169,17 @@ type RenderPageOptions = {
|
|
|
163
169
|
* silently skipped.
|
|
164
170
|
*/
|
|
165
171
|
watermarkImageSrc?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Paints the page in place of this module, returning the finished element.
|
|
174
|
+
*
|
|
175
|
+
* The one seam a second page renderer needs: everything around it (page
|
|
176
|
+
* shells, virtualization, the fingerprint comparison, the intersection
|
|
177
|
+
* observer, the painted event) is a page-container concern rather than a
|
|
178
|
+
* painting one, and stays shared. Returning `null` falls back to this
|
|
179
|
+
* module for that page, so a renderer that cannot express a page yields
|
|
180
|
+
* rather than painting it wrong.
|
|
181
|
+
*/
|
|
182
|
+
paintPage?: (request: PaintPageRequest) => HTMLElement | null;
|
|
166
183
|
};
|
|
167
184
|
type HeaderFooterLayoutInfo = {
|
|
168
185
|
flowTop: number;
|
|
@@ -242,6 +259,17 @@ declare function renderFootnoteArea(footnotes: FootnoteRenderItem[], contentWidt
|
|
|
242
259
|
* @returns The page DOM element
|
|
243
260
|
*/
|
|
244
261
|
declare function renderPage(page: Page, context: RenderContext, options?: RenderPageOptions): HTMLElement;
|
|
262
|
+
/**
|
|
263
|
+
* Which header and footer part a page selects, by `w:titlePg` and
|
|
264
|
+
* `w:evenAndOddHeaders`. Exported because a second consumer needs to tell "this
|
|
265
|
+
* page selects no part" from "this page selects a part nobody supplied", and
|
|
266
|
+
* recomputing the rule to answer that is how the two would come to disagree
|
|
267
|
+
* about which header a page carries. `undefined` means the page selects none.
|
|
268
|
+
*/
|
|
269
|
+
declare function selectSectionHeaderFooterRIds(page: Page): {
|
|
270
|
+
headerRId: string | undefined;
|
|
271
|
+
footerRId: string | undefined;
|
|
272
|
+
};
|
|
245
273
|
declare function applySectionHeaderFooterOptions(page: Page, pageOptions: RenderPageOptions, options: RenderPageOptions): boolean;
|
|
246
274
|
/**
|
|
247
275
|
* Compute a fingerprint string for a page that changes when its content changes.
|
|
@@ -302,4 +330,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
|
|
|
302
330
|
isExact: boolean;
|
|
303
331
|
} | null;
|
|
304
332
|
//#endregion
|
|
305
|
-
export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition };
|
|
333
|
+
export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PaintPageRequest, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition, selectSectionHeaderFooterRIds };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveFloatingTableX } from "../layout-engine/measure/floatingTablePosition.js";
|
|
2
2
|
import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
|
|
3
3
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
4
|
+
import { resolveTableInlineOffset } from "../layout-engine/measure/tableInlinePlacement.js";
|
|
4
5
|
import { floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
|
|
5
6
|
import { resolveFontFamily } from "../utils/fontResolver.js";
|
|
6
7
|
import { borderToStyle } from "../utils/formatToStyle.js";
|
|
@@ -504,10 +505,11 @@ function renderHeaderFooterContent(content, context, options, layout) {
|
|
|
504
505
|
fragEl.style.left = `${left}px`;
|
|
505
506
|
containerEl.append(fragEl);
|
|
506
507
|
} else {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
508
|
+
const inlineLeft = resolveTableInlineOffset({
|
|
509
|
+
table: block,
|
|
510
|
+
frameWidth: contentWidth,
|
|
511
|
+
tableWidth: measure.totalWidth
|
|
512
|
+
});
|
|
511
513
|
fragEl.style.position = "absolute";
|
|
512
514
|
fragEl.style.top = `${cursorY}px`;
|
|
513
515
|
fragEl.style.left = `${inlineLeft}px`;
|
|
@@ -816,6 +818,12 @@ function getFootnoteMeasureHeight(measure) {
|
|
|
816
818
|
*/
|
|
817
819
|
function renderPage(page, context, options = {}) {
|
|
818
820
|
const doc = options.document ?? document;
|
|
821
|
+
const painted = options.paintPage?.({
|
|
822
|
+
page,
|
|
823
|
+
context,
|
|
824
|
+
options
|
|
825
|
+
});
|
|
826
|
+
if (painted) return painted;
|
|
819
827
|
const pageEl = doc.createElement("div");
|
|
820
828
|
pageEl.className = options.pageClassName ?? PAGE_CLASS_NAMES.page;
|
|
821
829
|
pageEl.dataset["pageNumber"] = String(page.number);
|
|
@@ -1171,22 +1179,42 @@ function moveBehindHeaderFooterElementsToPage(slotEl, pageEl, contentTop, conten
|
|
|
1171
1179
|
pageEl.prepend(element);
|
|
1172
1180
|
}
|
|
1173
1181
|
}
|
|
1174
|
-
|
|
1182
|
+
/**
|
|
1183
|
+
* Which header and footer part a page selects, by `w:titlePg` and
|
|
1184
|
+
* `w:evenAndOddHeaders`. Exported because a second consumer needs to tell "this
|
|
1185
|
+
* page selects no part" from "this page selects a part nobody supplied", and
|
|
1186
|
+
* recomputing the rule to answer that is how the two would come to disagree
|
|
1187
|
+
* about which header a page carries. `undefined` means the page selects none.
|
|
1188
|
+
*/
|
|
1189
|
+
function selectSectionHeaderFooterRIds(page) {
|
|
1175
1190
|
const refs = page.headerFooterRefs;
|
|
1176
|
-
if (!refs) return
|
|
1177
|
-
|
|
1178
|
-
|
|
1191
|
+
if (!refs) return {
|
|
1192
|
+
headerRId: void 0,
|
|
1193
|
+
footerRId: void 0
|
|
1194
|
+
};
|
|
1195
|
+
const useFirst = refs.titlePg === true && page.sectionPageNumber === 1;
|
|
1179
1196
|
const useEven = refs.evenAndOddHeaders === true && page.logicalNumber % 2 === 0;
|
|
1180
|
-
const
|
|
1181
|
-
if (useFirst) return
|
|
1182
|
-
if (useEven) return
|
|
1183
|
-
return
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1197
|
+
const pick = (variants) => {
|
|
1198
|
+
if (useFirst) return variants.first;
|
|
1199
|
+
if (useEven) return variants.even;
|
|
1200
|
+
return variants.fallback;
|
|
1201
|
+
};
|
|
1202
|
+
return {
|
|
1203
|
+
headerRId: pick({
|
|
1204
|
+
first: refs.headerFirst,
|
|
1205
|
+
even: refs.headerEven,
|
|
1206
|
+
fallback: refs.headerDefault
|
|
1207
|
+
}),
|
|
1208
|
+
footerRId: pick({
|
|
1209
|
+
first: refs.footerFirst,
|
|
1210
|
+
even: refs.footerEven,
|
|
1211
|
+
fallback: refs.footerDefault
|
|
1212
|
+
})
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function applySectionHeaderFooterOptions(page, pageOptions, options) {
|
|
1216
|
+
if (!page.headerFooterRefs) return false;
|
|
1217
|
+
const { headerRId, footerRId } = selectSectionHeaderFooterRIds(page);
|
|
1190
1218
|
if (headerRId) {
|
|
1191
1219
|
const content = options.headerContentByRId?.get(headerRId);
|
|
1192
1220
|
if (content) pageOptions.headerContent = content;
|
|
@@ -1864,4 +1892,4 @@ function depopulatePageShell(shell, pageDataMap) {
|
|
|
1864
1892
|
}
|
|
1865
1893
|
}
|
|
1866
1894
|
//#endregion
|
|
1867
|
-
export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition };
|
|
1895
|
+
export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft, resolveHeaderFooterIntrinsicFrameHorizontalPosition, selectSectionHeaderFooterRIds };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
|
|
1
|
+
import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabRun, TabStop, TextRun } from "../layout-engine/types.js";
|
|
2
2
|
import { RenderContext } from "./renderUtils.js";
|
|
3
3
|
import { PhysicalParagraphInlineLayout } from "../utils/paragraphInlineLayout.js";
|
|
4
4
|
//#region src/layout-painter/renderParagraph.d.ts
|
|
@@ -29,6 +29,11 @@ type RenderParagraphOptions = {
|
|
|
29
29
|
/** Inline image runs already rendered for this paragraph block */
|
|
30
30
|
renderedInlineImageKeys?: Set<string>;
|
|
31
31
|
};
|
|
32
|
+
declare function getRenderableTextColor(run: TextRun | TabRun): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Get leader character for tab
|
|
35
|
+
*/
|
|
36
|
+
declare function getLeaderChar(leader: string): string | null;
|
|
32
37
|
/**
|
|
33
38
|
* Slice runs for a specific line
|
|
34
39
|
*
|
|
@@ -47,6 +52,20 @@ declare function sliceRunsForLine(block: ParagraphBlock, line: MeasuredLine): Ru
|
|
|
47
52
|
* runs without CJK pass through unchanged (the common path).
|
|
48
53
|
*/
|
|
49
54
|
declare function splitTextRunsByEastAsia(runs: Run[]): Run[];
|
|
55
|
+
type CollapsibleLineEdgeSpacesResult = {
|
|
56
|
+
runs: Run[];
|
|
57
|
+
collapsedLeadingRuns: Set<TextRun>;
|
|
58
|
+
collapsedTrailingRuns: Set<TextRun>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Word keeps ordinary line-edge spaces addressable in the document model but
|
|
62
|
+
* gives them no painted advance. Collapsible trailing spaces always collapse;
|
|
63
|
+
* leading spaces collapse only after a soft wrap, not at the authored
|
|
64
|
+
* paragraph start or after a manual line break. Split paint runs to preserve
|
|
65
|
+
* exact PM ranges.
|
|
66
|
+
*/
|
|
67
|
+
declare const splitCollapsibleLineEdgeSpaces: (sourceRuns: Run[], collapseLeading: boolean) => CollapsibleLineEdgeSpacesResult;
|
|
68
|
+
declare const startsAfterSoftWrap: (block: ParagraphBlock, line: MeasuredLine) => boolean;
|
|
50
69
|
/**
|
|
51
70
|
* Options for rendering a line with justify support
|
|
52
71
|
*/
|
|
@@ -115,4 +134,4 @@ declare function renderLine(block: ParagraphBlock, line: MeasuredLine, alignment
|
|
|
115
134
|
*/
|
|
116
135
|
declare function renderParagraphFragment(fragment: ParagraphFragment, block: ParagraphBlock, measure: ParagraphMeasure, context: RenderContext, options?: RenderParagraphOptions): HTMLElement;
|
|
117
136
|
//#endregion
|
|
118
|
-
export { PARAGRAPH_CLASS_NAMES, RenderParagraphOptions, renderLine, renderParagraphFragment, sliceRunsForLine, splitTextRunsByEastAsia };
|
|
137
|
+
export { PARAGRAPH_CLASS_NAMES, RenderParagraphOptions, getLeaderChar, getRenderableTextColor, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
|
|
@@ -1671,4 +1671,4 @@ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting,
|
|
|
1671
1671
|
return span;
|
|
1672
1672
|
}
|
|
1673
1673
|
//#endregion
|
|
1674
|
-
export { PARAGRAPH_CLASS_NAMES, renderLine, renderParagraphFragment, sliceRunsForLine, splitTextRunsByEastAsia };
|
|
1674
|
+
export { PARAGRAPH_CLASS_NAMES, getLeaderChar, getRenderableTextColor, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
|
|
@@ -38,6 +38,30 @@ const TABLE_CLASS_NAMES = {
|
|
|
38
38
|
};
|
|
39
39
|
const CELL_DIAGONAL_BORDER_CLASS = "layout-table-cell-diagonal-border";
|
|
40
40
|
const CELL_BOTTOM_BORDER_CLASS = "layout-table-cell-bottom-border";
|
|
41
|
+
/**
|
|
42
|
+
* Quarter turn a `w:textDirection` puts on cell content (§17.18.93, plus the
|
|
43
|
+
* short spellings Word also writes).
|
|
44
|
+
*
|
|
45
|
+
* `btLr` runs bottom-to-top up the left edge, so it turns counter-clockwise;
|
|
46
|
+
* every top-to-bottom direction runs down the right edge and turns clockwise.
|
|
47
|
+
* The horizontal directions are the identity — `rl` is bidi, not rotation.
|
|
48
|
+
* Only `btLr` was handled, so a rotated header cell (the common `tbRl` shape)
|
|
49
|
+
* painted horizontally and overflowed its column.
|
|
50
|
+
*
|
|
51
|
+
* Total over the union on purpose: a new direction has to state its turn here
|
|
52
|
+
* rather than silently falling back to horizontal.
|
|
53
|
+
*/
|
|
54
|
+
const CELL_TEXT_ROTATION_DEGREES = {
|
|
55
|
+
lr: 0,
|
|
56
|
+
lrV: 0,
|
|
57
|
+
rl: 0,
|
|
58
|
+
rlV: 0,
|
|
59
|
+
tb: 90,
|
|
60
|
+
tbV: 90,
|
|
61
|
+
tbRl: 90,
|
|
62
|
+
tbRlV: 90,
|
|
63
|
+
btLr: -90
|
|
64
|
+
};
|
|
41
65
|
function renderCellContent({ cell, cellMeasure, context, doc, contentWidthOverride, pageContentPosition }) {
|
|
42
66
|
const contentEl = doc.createElement("div");
|
|
43
67
|
contentEl.className = TABLE_CLASS_NAMES.cellContent;
|
|
@@ -122,11 +146,19 @@ function renderCellContent({ cell, cellMeasure, context, doc, contentWidthOverri
|
|
|
122
146
|
...paragraphBlock.pmStart !== void 0 ? { pmStart: paragraphBlock.pmStart } : {},
|
|
123
147
|
...paragraphBlock.pmEnd !== void 0 ? { pmEnd: paragraphBlock.pmEnd } : {}
|
|
124
148
|
};
|
|
149
|
+
const previousBlock = cell.blocks[i - 1];
|
|
150
|
+
const nextBlock = cell.blocks[i + 1];
|
|
151
|
+
const prevBorders = previousBlock?.kind === "paragraph" ? previousBlock.attrs?.borders : void 0;
|
|
152
|
+
const nextBorders = nextBlock?.kind === "paragraph" ? nextBlock.attrs?.borders : void 0;
|
|
125
153
|
const cellContext = {
|
|
126
154
|
...context,
|
|
127
155
|
insideTableCell: true
|
|
128
156
|
};
|
|
129
|
-
const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, {
|
|
157
|
+
const fragEl = renderParagraphFragment(syntheticFragment, paragraphBlock, paragraphMeasure, cellContext, {
|
|
158
|
+
document: doc,
|
|
159
|
+
...prevBorders !== void 0 ? { prevBorders } : {},
|
|
160
|
+
...nextBorders !== void 0 ? { nextBorders } : {}
|
|
161
|
+
});
|
|
130
162
|
fragEl.style.position = "relative";
|
|
131
163
|
fragEl.style.boxSizing = "border-box";
|
|
132
164
|
fragEl.style.height = `${placement.leadingSpacing + placement.contentHeight}px`;
|
|
@@ -382,7 +414,8 @@ function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags,
|
|
|
382
414
|
default: break;
|
|
383
415
|
}
|
|
384
416
|
}
|
|
385
|
-
const
|
|
417
|
+
const cellTextRotation = cell.textDirection ? CELL_TEXT_ROTATION_DEGREES[cell.textDirection] : 0;
|
|
418
|
+
const contentWidthOverride = cellTextRotation === 0 ? void 0 : Math.max(1, rowHeight - padTop - padBottom);
|
|
386
419
|
const renderedContent = renderCellContent({
|
|
387
420
|
cell,
|
|
388
421
|
cellMeasure,
|
|
@@ -395,11 +428,11 @@ function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags,
|
|
|
395
428
|
y: pageContentPosition.y + padTop
|
|
396
429
|
} } : {}
|
|
397
430
|
});
|
|
398
|
-
if (
|
|
431
|
+
if (cellTextRotation !== 0) {
|
|
399
432
|
renderedContent.content.style.position = "absolute";
|
|
400
433
|
renderedContent.content.style.left = "50%";
|
|
401
434
|
renderedContent.content.style.top = "50%";
|
|
402
|
-
renderedContent.content.style.transform =
|
|
435
|
+
renderedContent.content.style.transform = `translate(-50%, -50%) rotate(${cellTextRotation}deg)`;
|
|
403
436
|
}
|
|
404
437
|
if (renderedContent.floatingLayers.length > 0) {
|
|
405
438
|
renderedContent.content.style.height = "100%";
|
|
@@ -30,6 +30,38 @@ type ClassifyEditorKeydownOptions = {
|
|
|
30
30
|
* - Delete/Backspace (no modifiers, focus not in an input) → delete selected table
|
|
31
31
|
*/
|
|
32
32
|
declare function classifyEditorKeydown(e: KeyboardEvent, { isMac, isInputLike }: ClassifyEditorKeydownOptions): EditorKeydownIntent;
|
|
33
|
+
/**
|
|
34
|
+
* Which key presses the editor's page-level shortcuts answer.
|
|
35
|
+
* - `"document"`: every press on the page.
|
|
36
|
+
* - `"editor"`: only a press whose target is inside the editor.
|
|
37
|
+
* - `"none"`: no page-level listener; the host dispatches shortcuts itself.
|
|
38
|
+
*/
|
|
39
|
+
type KeyboardShortcutScope = "document" | "editor" | "none";
|
|
40
|
+
/**
|
|
41
|
+
* The containment surface a scope root has to offer. Structural rather than
|
|
42
|
+
* `Element` so the predicate carries no DOM dependency and answers the same
|
|
43
|
+
* under a server render and a DOM-less test runtime.
|
|
44
|
+
*/
|
|
45
|
+
type ShortcutScopeRoot = {
|
|
46
|
+
contains(other: unknown): boolean;
|
|
47
|
+
};
|
|
48
|
+
type IsKeydownInShortcutScopeOptions = {
|
|
49
|
+
scope: KeyboardShortcutScope;
|
|
50
|
+
/**
|
|
51
|
+
* Elements a press must land inside under `"editor"`: the editor root plus
|
|
52
|
+
* any surface it owns outside that subtree (a portaled dialog). A nullish
|
|
53
|
+
* entry — an unmounted ref — never matches.
|
|
54
|
+
*/
|
|
55
|
+
roots: readonly (ShortcutScopeRoot | null | undefined)[];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Whether a keydown is one the editor answers under `scope`. The adapters own
|
|
59
|
+
* the listener; this decides, from the event target alone, whether the press
|
|
60
|
+
* belongs to this editor or to whatever else the host has on the page.
|
|
61
|
+
*/
|
|
62
|
+
declare function isKeydownInShortcutScope(event: {
|
|
63
|
+
target: unknown;
|
|
64
|
+
}, { scope, roots }: IsKeydownInShortcutScopeOptions): boolean;
|
|
33
65
|
/**
|
|
34
66
|
* If the selection is a ProseMirror `CellSelection` covering every cell of its
|
|
35
67
|
* table, delete the table and return `true`. Returns `false` for any other
|
|
@@ -37,4 +69,4 @@ declare function classifyEditorKeydown(e: KeyboardEvent, { isMac, isInputLike }:
|
|
|
37
69
|
*/
|
|
38
70
|
declare function deleteSelectedTable(state: EditorState, dispatch: (tr: Transaction) => void): boolean;
|
|
39
71
|
//#endregion
|
|
40
|
-
export { ClassifyEditorKeydownOptions, EditorKeydownIntent, classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isMacPlatform };
|
|
72
|
+
export { ClassifyEditorKeydownOptions, EditorKeydownIntent, IsKeydownInShortcutScopeOptions, KeyboardShortcutScope, ShortcutScopeRoot, classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };
|