@stll/folio-core 0.47.7 → 0.48.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/controller/fontReadiness.js +3 -3
- package/dist/controller/hyphenationReadiness.d.ts +19 -0
- package/dist/controller/hyphenationReadiness.js +47 -0
- package/dist/controller/layoutPipeline.d.ts +3 -0
- package/dist/controller/layoutPipeline.js +59 -17
- package/dist/display-list/build/buildDisplayList.js +39 -12
- package/dist/display-list/build/pageFurniture.d.ts +7 -1
- package/dist/display-list/build/pageFurniture.js +21 -8
- package/dist/display-list/build/paragraphPrimitives.js +5 -4
- package/dist/display-list/build/tablePrimitives.js +10 -6
- package/dist/docx/attributeRemainder.js +10 -2
- package/dist/docx/blockContentParser.d.ts +2 -0
- package/dist/docx/blockContentParser.js +77 -62
- package/dist/docx/commentParser.d.ts +2 -1
- package/dist/docx/commentParser.js +57 -45
- package/dist/docx/containerChildren.d.ts +21 -8
- package/dist/docx/containerChildren.js +39 -9
- package/dist/docx/documentParser.d.ts +5 -1
- package/dist/docx/documentParser.js +8 -5
- package/dist/docx/documentSectionFacts.d.ts +22 -0
- package/dist/docx/documentSectionFacts.js +43 -0
- package/dist/docx/fieldParser.d.ts +8 -1
- package/dist/docx/fieldParser.js +47 -3
- package/dist/docx/fontTableParser.js +34 -27
- package/dist/docx/footnoteParser.d.ts +3 -2
- package/dist/docx/footnoteParser.js +15 -12
- package/dist/docx/groupDrawingParser.d.ts +2 -1
- package/dist/docx/groupDrawingParser.js +3 -6
- package/dist/docx/headerFooterParser.d.ts +7 -2
- package/dist/docx/headerFooterParser.js +15 -5
- package/dist/docx/hyperlinkParser.d.ts +48 -36
- package/dist/docx/hyperlinkParser.js +97 -82
- package/dist/docx/numberingParser.js +108 -84
- package/dist/docx/paragraphParser.d.ts +5 -1
- package/dist/docx/paragraphParser.js +381 -362
- package/dist/docx/paragraphProperties.js +123 -109
- package/dist/docx/paragraphPropertySource.d.ts +6 -1
- package/dist/docx/paragraphPropertySource.js +9 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +17 -6
- package/dist/docx/parser.d.ts +7 -1
- package/dist/docx/parser.js +33 -16
- package/dist/docx/previewBudget.d.ts +55 -33
- package/dist/docx/previewBudget.js +59 -65
- package/dist/docx/rezip.js +41 -58
- package/dist/docx/runParser.d.ts +4 -1
- package/dist/docx/runParser.js +172 -184
- package/dist/docx/sdtProperties.js +87 -84
- package/dist/docx/sectionParser.js +169 -160
- package/dist/docx/serializer/paragraphSerializer.js +42 -38
- package/dist/docx/serializer/settingsSerializer.js +19 -1
- package/dist/docx/settingsParser.js +8 -0
- package/dist/docx/streamingXmlParser.d.ts +6 -2
- package/dist/docx/streamingXmlParser.js +12 -7
- package/dist/docx/styleParser.js +34 -8
- package/dist/docx/tableParser.d.ts +21 -12
- package/dist/docx/tableParser.js +518 -441
- package/dist/docx/textBoxParser.d.ts +8 -3
- package/dist/docx/textBoxParser.js +3 -3
- package/dist/docx/verbatimCapture.d.ts +10 -8
- package/dist/docx/verbatimCapture.js +45 -2
- package/dist/docx/vmlImageParser.d.ts +15 -13
- package/dist/docx/vmlImageParser.js +75 -60
- package/dist/docx/vmlPreview.d.ts +3 -3
- package/dist/docx/vmlPreview.js +3 -4
- package/dist/docx/xmlNamespaceContext.d.ts +9 -0
- package/dist/docx/xmlNamespaceContext.js +54 -0
- package/dist/docx/xmlParser.d.ts +6 -3
- package/dist/docx/xmlParser.js +11 -41
- package/dist/docx/xmlResourceLimits.d.ts +2 -1
- package/dist/docx/xmlResourceLimits.js +4 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/headless-layout.d.ts +11 -2
- package/dist/headless-layout.js +153 -107
- package/dist/internal/paragraphFormattingSerialization.js +3 -3
- package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
- package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
- package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
- package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
- package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +258 -35
- package/dist/layout-bridge/engine/hitTest.js +5 -3
- package/dist/layout-bridge/engine/selectionRects.js +5 -3
- package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
- package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +70 -11
- package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
- package/dist/layout-engine/justificationCompatibility.js +9 -5
- package/dist/layout-engine/keep-together.d.ts +11 -5
- package/dist/layout-engine/keep-together.js +39 -16
- package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
- package/dist/layout-engine/layoutInstrumentation.js +8 -1
- package/dist/layout-engine/measure/advanceComposition.js +4 -7
- package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
- package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
- package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
- package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
- package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
- package/dist/layout-engine/measure/lineBreaks.js +10 -3
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
- package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
- package/dist/layout-engine/measure/measureBlocks.js +42 -8
- package/dist/layout-engine/measure/measureContainer.js +5 -5
- package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
- package/dist/layout-engine/measure/measureHelpers.js +21 -5
- package/dist/layout-engine/measure/measureParagraph.js +109 -30
- package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
- package/dist/layout-engine/measure/tabCalculator.js +2 -1
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +51 -19
- package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
- package/dist/layout-engine/measure/tableCellGrid.js +45 -5
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
- package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
- package/dist/layout-engine/noteAreaFlow.js +83 -0
- package/dist/layout-engine/paginator.d.ts +10 -1
- package/dist/layout-engine/paginator.js +13 -1
- package/dist/layout-engine/tableRowBreak.js +3 -1
- package/dist/layout-engine/types.d.ts +89 -3
- package/dist/layout-engine/types.js +96 -1
- package/dist/layout-painter/index.d.ts +3 -0
- package/dist/layout-painter/renderPage.js +32 -83
- package/dist/layout-painter/renderParagraph.d.ts +6 -1
- package/dist/layout-painter/renderParagraph.js +34 -22
- package/dist/layout-painter/renderTable.js +26 -13
- package/dist/prosemirror/attrs/index.d.ts +9 -9
- package/dist/prosemirror/attrs/index.js +43 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +8 -1
- package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
- package/dist/prosemirror/conversion/markInterner.js +90 -0
- package/dist/prosemirror/conversion/toProseDoc.js +77 -41
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +2 -1
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +2 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +13 -1
- package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
- package/dist/prosemirror/textBoxHostParagraph.js +21 -0
- package/dist/prosemirror/validation.js +91 -80
- package/dist/utils/createDocument.js +10 -1
- package/dist/utils/scriptSegments.d.ts +33 -4
- package/dist/utils/scriptSegments.js +39 -6
- package/dist/utils/textFormattingMerge.d.ts +17 -3
- package/dist/utils/textFormattingMerge.js +18 -12
- package/dist/utils/trailingText.d.ts +22 -0
- package/dist/utils/trailingText.js +32 -0
- package/package.json +2 -2
|
@@ -2,8 +2,20 @@ import { RunFormatting } from "../types.js";
|
|
|
2
2
|
import { FontStyle } from "./measureTypes.js";
|
|
3
3
|
//#region src/layout-engine/measure/measureHelpers.d.ts
|
|
4
4
|
declare const DEFAULT_FONT_SIZE = 11;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The face for text whose run properties name no font anywhere in the cascade:
|
|
7
|
+
* no `w:rFonts` family or theme reference in the run, its styles or
|
|
8
|
+
* `w:docDefaults`. ECMA-376 §17.3.2.26 leaves this to the application; its
|
|
9
|
+
* default is Times New Roman. A document that names a font at any level never
|
|
10
|
+
* reaches it.
|
|
11
|
+
*/
|
|
12
|
+
declare const DEFAULT_FONT_FAMILY = "Times New Roman";
|
|
13
|
+
/**
|
|
14
|
+
* Point size of `w:vertAlign="superscript"`/`"subscript"` text: two thirds
|
|
15
|
+
* of the run's size, truncated to whole half-points (the `w:sz` unit) and
|
|
16
|
+
* never below one half-point.
|
|
17
|
+
*/
|
|
18
|
+
declare function docxScriptFontSize(fontSize: number): number;
|
|
7
19
|
/**
|
|
8
20
|
* Build a measurement `FontStyle` from a run's formatting. Single source of
|
|
9
21
|
* truth for run → FontStyle so every measurement path (layout line-breaking,
|
|
@@ -88,4 +100,4 @@ declare function halfPtToPx(halfPt: number): number;
|
|
|
88
100
|
*/
|
|
89
101
|
declare function pxToHalfPt(px: number): number;
|
|
90
102
|
//#endregion
|
|
91
|
-
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE,
|
|
103
|
+
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, docxScriptFontSize, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveFontFamily } from "../../utils/fontResolver.js";
|
|
2
2
|
import "../../utils/fontWeights.js";
|
|
3
3
|
import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
|
|
4
|
+
import { eastAsiaHintApplies } from "../../utils/scriptSegments.js";
|
|
4
5
|
import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolicy.js";
|
|
5
6
|
//#region src/layout-engine/measure/measureHelpers.ts
|
|
6
7
|
/**
|
|
@@ -16,8 +17,22 @@ const TWIPS_PER_INCH = 1440;
|
|
|
16
17
|
const PX_PER_INCH = 96;
|
|
17
18
|
const TWIPS_PER_PX = TWIPS_PER_INCH / PX_PER_INCH;
|
|
18
19
|
const DEFAULT_FONT_SIZE = 11;
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The face for text whose run properties name no font anywhere in the cascade:
|
|
22
|
+
* no `w:rFonts` family or theme reference in the run, its styles or
|
|
23
|
+
* `w:docDefaults`. ECMA-376 §17.3.2.26 leaves this to the application; its
|
|
24
|
+
* default is Times New Roman. A document that names a font at any level never
|
|
25
|
+
* reaches it.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_FONT_FAMILY = "Times New Roman";
|
|
28
|
+
/**
|
|
29
|
+
* Point size of `w:vertAlign="superscript"`/`"subscript"` text: two thirds
|
|
30
|
+
* of the run's size, truncated to whole half-points (the `w:sz` unit) and
|
|
31
|
+
* never below one half-point.
|
|
32
|
+
*/
|
|
33
|
+
function docxScriptFontSize(fontSize) {
|
|
34
|
+
return Math.max(1, Math.floor(fontSize * 2 * 2 / 3)) / 2;
|
|
35
|
+
}
|
|
21
36
|
/**
|
|
22
37
|
* Build a measurement `FontStyle` from a run's formatting. Single source of
|
|
23
38
|
* truth for run → FontStyle so every measurement path (layout line-breaking,
|
|
@@ -28,13 +43,14 @@ const DOCX_SCRIPT_FONT_SCALE = .75;
|
|
|
28
43
|
*/
|
|
29
44
|
function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
|
|
30
45
|
const baseFontSize = run.fontSize ?? fallbackFontSize;
|
|
31
|
-
const fontSize = run.superscript || run.subscript ? baseFontSize
|
|
46
|
+
const fontSize = run.superscript || run.subscript ? docxScriptFontSize(baseFontSize) : baseFontSize;
|
|
32
47
|
const horizontalScale = normalizeHorizontalScalePercent(run.horizontalScale);
|
|
33
48
|
return {
|
|
34
49
|
fontFamily: run.fontFamily ?? fallbackFontFamily,
|
|
35
50
|
...run.alternateFontFamily !== void 0 ? { alternateFontFamily: run.alternateFontFamily } : {},
|
|
36
51
|
...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
|
|
37
52
|
...run.eastAsiaAlternateFontFamily !== void 0 ? { eastAsiaAlternateFontFamily: run.eastAsiaAlternateFontFamily } : {},
|
|
53
|
+
...eastAsiaHintApplies(run) ? { eastAsiaHint: true } : {},
|
|
38
54
|
...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
|
|
39
55
|
...run.complexScriptAlternateFontFamily !== void 0 ? { complexScriptAlternateFontFamily: run.complexScriptAlternateFontFamily } : {},
|
|
40
56
|
...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
|
|
@@ -107,7 +123,7 @@ function buildFontString(style) {
|
|
|
107
123
|
if (style.bold) parts.push("700");
|
|
108
124
|
const fontSizePx = ptToPx(style.fontSize ?? 11);
|
|
109
125
|
parts.push(`${fontSizePx}px`);
|
|
110
|
-
const fontFamily = style.fontFamily ?? "
|
|
126
|
+
const fontFamily = style.fontFamily ?? "Times New Roman";
|
|
111
127
|
parts.push(getResolvedFallback(fontFamily, style.alternateFontFamily));
|
|
112
128
|
return parts.join(" ");
|
|
113
129
|
}
|
|
@@ -181,4 +197,4 @@ function pxToHalfPt(px) {
|
|
|
181
197
|
return pxToPt(px) * 2;
|
|
182
198
|
}
|
|
183
199
|
//#endregion
|
|
184
|
-
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE,
|
|
200
|
+
export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, docxScriptFontSize, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
2
|
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
3
3
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
4
|
-
import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
|
|
4
|
+
import { hasCjk, hasComplexScript, hasEastAsiaSlotText } from "../../utils/scriptSegments.js";
|
|
5
|
+
import { splitTrailingToken } from "../../utils/trailingText.js";
|
|
5
6
|
import { JUSTIFIED_FINAL_LINE_MAX_SHRINK_RATIO, JUSTIFIED_FINAL_LINE_SPACE_CONTRACTION_RATIO, supportsJustifiedFinalLineContraction } from "../justifiedLineFit.js";
|
|
6
7
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
7
8
|
import { isFloatingImageRun } from "../types.js";
|
|
@@ -10,9 +11,9 @@ import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./comp
|
|
|
10
11
|
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
11
12
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
12
13
|
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
13
|
-
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
14
|
+
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakBetween, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
14
15
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
15
|
-
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
16
|
+
import { DEFAULT_FONT_FAMILY, buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
16
17
|
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
17
18
|
import { calculateTabWidth, pixelsToTwips } from "./tabCalculator.js";
|
|
18
19
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
@@ -24,11 +25,10 @@ import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
|
24
25
|
* Converts runs into measured lines with typography metrics.
|
|
25
26
|
*/
|
|
26
27
|
const DEFAULT_FONT_SIZE = 11;
|
|
27
|
-
const DEFAULT_FONT_FAMILY = "Calibri";
|
|
28
28
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
29
29
|
const WIDTH_TOLERANCE = .5;
|
|
30
30
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
31
|
-
const JUSTIFY_SPACE_CONTRACTION_RATIO = .
|
|
31
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .25;
|
|
32
32
|
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
33
33
|
const JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO = .022;
|
|
34
34
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
@@ -85,9 +85,11 @@ function runToFontStyle(run) {
|
|
|
85
85
|
* keeps the base style so wrapping is untouched.
|
|
86
86
|
*/
|
|
87
87
|
function cjkLineHeightStyle(run, baseStyle) {
|
|
88
|
-
if (!run.text
|
|
88
|
+
if (!run.text) return baseStyle;
|
|
89
89
|
const eastAsia = baseStyle.eastAsiaFontFamily;
|
|
90
|
-
|
|
90
|
+
const eastAsiaFace = eastAsia !== void 0 && isCjkFont(eastAsia);
|
|
91
|
+
if (!hasCjk(run.text) && !(eastAsiaFace && hasEastAsiaSlotText(run.text, baseStyle.eastAsiaHint))) return baseStyle;
|
|
92
|
+
if (eastAsia !== void 0 && eastAsiaFace) {
|
|
91
93
|
const result = {
|
|
92
94
|
...baseStyle,
|
|
93
95
|
fontFamily: eastAsia
|
|
@@ -96,7 +98,7 @@ function cjkLineHeightStyle(run, baseStyle) {
|
|
|
96
98
|
if (baseStyle.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = baseStyle.eastAsiaAlternateFontFamily;
|
|
97
99
|
return result;
|
|
98
100
|
}
|
|
99
|
-
if (isCjkFont(baseStyle.fontFamily ??
|
|
101
|
+
if (isCjkFont(baseStyle.fontFamily ?? "Times New Roman")) return baseStyle;
|
|
100
102
|
return {
|
|
101
103
|
...baseStyle,
|
|
102
104
|
fontFamily: CJK_FALLBACK_FONT_FAMILY
|
|
@@ -153,7 +155,7 @@ const WORD_SINGLE_LINE_FLOOR = 1.15;
|
|
|
153
155
|
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
|
|
154
156
|
const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
|
|
155
157
|
fontSize,
|
|
156
|
-
fontFamily: fontFamily ??
|
|
158
|
+
fontFamily: fontFamily ?? "Times New Roman",
|
|
157
159
|
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
158
160
|
}));
|
|
159
161
|
const lineRule = spacing?.lineRule ?? "auto";
|
|
@@ -432,15 +434,13 @@ function isFinalTextCandidate(block, runIndex, nextBreak) {
|
|
|
432
434
|
return true;
|
|
433
435
|
}
|
|
434
436
|
function resolveTextCandidateFit({ block, line, isFirstLine, isFinalCandidate, candidateWidth, candidateSpaceWidth, fallbackTolerancePx, continuationStrategy, finalStrategy }) {
|
|
435
|
-
if (!isFinalCandidate
|
|
437
|
+
if (!isFinalCandidate) return {
|
|
436
438
|
type: "ordinary",
|
|
437
439
|
tolerancePx: fallbackTolerancePx
|
|
438
440
|
};
|
|
441
|
+
if (!supportsJustifiedFinalLineContraction(block)) return admitFinalCandidate(candidateWidth, line.availableWidth, fallbackTolerancePx);
|
|
439
442
|
const ordinaryTolerancePx = isFirstLine ? fallbackTolerancePx : justifyFitTolerance(line, continuationStrategy, candidateSpaceWidth);
|
|
440
|
-
if (candidateWidth <= line.availableWidth + ordinaryTolerancePx) return
|
|
441
|
-
type: "ordinary",
|
|
442
|
-
tolerancePx: ordinaryTolerancePx
|
|
443
|
-
};
|
|
443
|
+
if (candidateWidth <= line.availableWidth + ordinaryTolerancePx) return admitFinalCandidate(candidateWidth, line.availableWidth, ordinaryTolerancePx);
|
|
444
444
|
const finalTolerancePx = justifyFitTolerance(line, finalStrategy, candidateSpaceWidth);
|
|
445
445
|
if (candidateWidth > line.availableWidth + finalTolerancePx) return {
|
|
446
446
|
type: "final-contraction-rejected",
|
|
@@ -455,6 +455,26 @@ function resolveTextCandidateFit({ block, line, isFirstLine, isFinalCandidate, c
|
|
|
455
455
|
}
|
|
456
456
|
};
|
|
457
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* A paragraph's last line is not stretched, so a final word admitted by space
|
|
460
|
+
* contraction needs an explicit paint plan; otherwise it would paint past the
|
|
461
|
+
* measure. Sub-pixel rounding overflow stays on the ordinary path.
|
|
462
|
+
*/
|
|
463
|
+
function admitFinalCandidate(candidateWidth, availableWidth, tolerancePx) {
|
|
464
|
+
const contractionPx = candidateWidth - availableWidth;
|
|
465
|
+
if (contractionPx <= WIDTH_TOLERANCE || contractionPx > tolerancePx) return {
|
|
466
|
+
type: "ordinary",
|
|
467
|
+
tolerancePx
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
type: "final-contraction-admitted",
|
|
471
|
+
tolerancePx,
|
|
472
|
+
paint: {
|
|
473
|
+
type: "space-contraction",
|
|
474
|
+
contractionPx
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
458
478
|
function compressibleSpaceWidth(text, style) {
|
|
459
479
|
const count = countCompressibleSpaces(text);
|
|
460
480
|
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
@@ -503,6 +523,21 @@ function measureWordWithTrailingWhitespace(word, style) {
|
|
|
503
523
|
fullWordWidth: wordWidth + trailingWhitespaceWidth + boundarySpacing
|
|
504
524
|
};
|
|
505
525
|
}
|
|
526
|
+
const scaledLetterSpacing = (style) => (style.letterSpacing ?? 0) * getHorizontalScaleFactor(style.horizontalScale);
|
|
527
|
+
/**
|
|
528
|
+
* Character spacing still owed after the last character placed on the line,
|
|
529
|
+
* or 0 when the line has no preceding character at `charIndex` of `runIndex`.
|
|
530
|
+
* Within a run it is the run's own spacing; at a boundary between two text
|
|
531
|
+
* runs it is the preceding run's spacing on its final character.
|
|
532
|
+
*/
|
|
533
|
+
function precedingLetterSpacing({ block, line, runIndex, charIndex, runStyle }) {
|
|
534
|
+
if (line.fromRun === line.toRun && line.fromChar === line.toChar) return 0;
|
|
535
|
+
if (line.toRun === runIndex && line.toChar === charIndex) return scaledLetterSpacing(runStyle);
|
|
536
|
+
if (charIndex !== 0 || line.toRun !== runIndex - 1) return 0;
|
|
537
|
+
const previousRun = block.runs[runIndex - 1];
|
|
538
|
+
if (!previousRun || !isTextRun(previousRun) || previousRun.text.length === 0 || line.toChar !== previousRun.text.length) return 0;
|
|
539
|
+
return scaledLetterSpacing(runToFontStyle(previousRun));
|
|
540
|
+
}
|
|
506
541
|
function trailingCodePoint(text) {
|
|
507
542
|
if (text.length === 0) return;
|
|
508
543
|
const trailing = text.charCodeAt(text.length - 1);
|
|
@@ -548,6 +583,21 @@ function computeTrailingGlueWidths(block) {
|
|
|
548
583
|
}
|
|
549
584
|
return widths;
|
|
550
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* First character of the text that follows each run, looking through empty
|
|
588
|
+
* text runs; undefined before a non-text run or at the paragraph end.
|
|
589
|
+
*/
|
|
590
|
+
function computeFollowingTextLeads(block) {
|
|
591
|
+
const leads = Array.from({ length: block.runs.length }, () => void 0);
|
|
592
|
+
let lead;
|
|
593
|
+
for (let index = block.runs.length - 1; index >= 0; index--) {
|
|
594
|
+
leads[index] = lead;
|
|
595
|
+
const run = block.runs[index];
|
|
596
|
+
if (!run || !isTextRun(run)) lead = void 0;
|
|
597
|
+
else if (run.text) lead = run.text[0];
|
|
598
|
+
}
|
|
599
|
+
return leads;
|
|
600
|
+
}
|
|
551
601
|
function computeProtectedCrossRunGlueWidths(block) {
|
|
552
602
|
const widths = Array.from({ length: block.runs.length }, () => 0);
|
|
553
603
|
for (let index = 0; index < block.runs.length; index++) {
|
|
@@ -558,10 +608,10 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
558
608
|
run
|
|
559
609
|
}).provider;
|
|
560
610
|
if (!policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
561
|
-
const trailing =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
let separator = trailing
|
|
611
|
+
const trailing = splitTrailingToken(run.text ?? "");
|
|
612
|
+
if (!trailing || trailing.token.length !== 1) continue;
|
|
613
|
+
const { token } = trailing;
|
|
614
|
+
let separator = trailing.separator;
|
|
565
615
|
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
566
616
|
let followingWord = "";
|
|
567
617
|
let unseparatedFollowingText = false;
|
|
@@ -772,7 +822,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
772
822
|
};
|
|
773
823
|
}
|
|
774
824
|
if (runs.length === 0) {
|
|
775
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ??
|
|
825
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? "Times New Roman", attrs);
|
|
776
826
|
const outlineLineHeight = attrs?.reserveEmptyOutlineHeight ? emptyMetrics.lineHeight * 2 : emptyMetrics.lineHeight;
|
|
777
827
|
lines.push({
|
|
778
828
|
fromRun: 0,
|
|
@@ -793,7 +843,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
793
843
|
};
|
|
794
844
|
}
|
|
795
845
|
if (runs.length > 0 && runs.every((run) => isTextRun(run) && isEmptyTextRun(run))) {
|
|
796
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ??
|
|
846
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? "Times New Roman", attrs);
|
|
797
847
|
lines.push({
|
|
798
848
|
fromRun: 0,
|
|
799
849
|
fromChar: 0,
|
|
@@ -813,6 +863,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
813
863
|
}
|
|
814
864
|
const trailingGlueWidths = computeTrailingGlueWidths(block);
|
|
815
865
|
const protectedCrossRunGlueWidths = computeProtectedCrossRunGlueWidths(block);
|
|
866
|
+
const followingTextLeads = computeFollowingTextLeads(block);
|
|
816
867
|
let currentLine = {
|
|
817
868
|
fromRun: 0,
|
|
818
869
|
fromChar: 0,
|
|
@@ -823,6 +874,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
823
874
|
regularSpaceWidth: 0,
|
|
824
875
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
825
876
|
maxFontMetrics: null,
|
|
877
|
+
maxTabFontSize: DEFAULT_FONT_SIZE,
|
|
878
|
+
maxTabFontMetrics: null,
|
|
826
879
|
maxImageHeightPx: 0,
|
|
827
880
|
maxExactImageHeightPx: 0,
|
|
828
881
|
maxMathHeightPx: 0,
|
|
@@ -833,9 +886,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
833
886
|
};
|
|
834
887
|
const calculateLineTypography = (line) => {
|
|
835
888
|
const paragraphFontSize = attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
836
|
-
const paragraphFontFamily = attrs?.defaultFontFamily ??
|
|
837
|
-
|
|
838
|
-
|
|
889
|
+
const paragraphFontFamily = attrs?.defaultFontFamily ?? "Times New Roman";
|
|
890
|
+
let lineFontMetrics = line.maxFontMetrics;
|
|
891
|
+
let fontSize = line.maxFontSize;
|
|
892
|
+
if (!lineFontMetrics) {
|
|
893
|
+
lineFontMetrics = line.maxTabFontMetrics;
|
|
894
|
+
fontSize = lineFontMetrics ? line.maxTabFontSize : paragraphFontSize;
|
|
895
|
+
}
|
|
896
|
+
const metrics = lineFontMetrics ?? getFontMetrics({
|
|
839
897
|
fontSize: paragraphFontSize,
|
|
840
898
|
fontFamily: paragraphFontFamily,
|
|
841
899
|
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
@@ -919,6 +977,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
919
977
|
regularSpaceWidth: 0,
|
|
920
978
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
921
979
|
maxFontMetrics: null,
|
|
980
|
+
maxTabFontSize: DEFAULT_FONT_SIZE,
|
|
981
|
+
maxTabFontMetrics: null,
|
|
922
982
|
maxImageHeightPx: 0,
|
|
923
983
|
maxExactImageHeightPx: 0,
|
|
924
984
|
maxMathHeightPx: 0,
|
|
@@ -942,6 +1002,19 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
942
1002
|
const metrics = getFontMetrics(style);
|
|
943
1003
|
if (metrics.singleLineRatio > currentLine.maxFontMetrics.singleLineRatio) currentLine.maxFontMetrics = metrics;
|
|
944
1004
|
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Track the tallest tab on the line separately from content runs: a tab's
|
|
1007
|
+
* font does not raise a line that carries other content, so a tab run
|
|
1008
|
+
* formatted larger than its neighbouring text (a TOC entry whose tab keeps
|
|
1009
|
+
* the paragraph-mark size) leaves the line at the text's height.
|
|
1010
|
+
*/
|
|
1011
|
+
const updateMaxTabFont = (style) => {
|
|
1012
|
+
const fontSize = style.fontSize ?? DEFAULT_FONT_SIZE;
|
|
1013
|
+
if (!currentLine.maxTabFontMetrics || fontSize > currentLine.maxTabFontSize) {
|
|
1014
|
+
currentLine.maxTabFontSize = fontSize;
|
|
1015
|
+
currentLine.maxTabFontMetrics = getFontMetrics(style);
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
945
1018
|
let crossRunResume;
|
|
946
1019
|
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
947
1020
|
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
@@ -970,7 +1043,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
970
1043
|
}
|
|
971
1044
|
if (isTabRun(run)) {
|
|
972
1045
|
const style = runToFontStyle(run);
|
|
973
|
-
|
|
1046
|
+
updateMaxTabFont(style);
|
|
974
1047
|
const followingWidth = measureInlineWidthAfterTab(runs, runIndex, options?.fieldValues);
|
|
975
1048
|
const decimalPrefixWidth = measureDecimalPrefixWidthAfterTab(runs, runIndex, options?.fieldValues);
|
|
976
1049
|
const lineX = currentLine.width + currentLine.leftOffset;
|
|
@@ -987,14 +1060,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
987
1060
|
let tabWidth = tabResult.width;
|
|
988
1061
|
const authoredEndpoint = contentX + tabWidth + followingWidth;
|
|
989
1062
|
const activeContentRightEdge = maxWidth - currentLine.rightOffset;
|
|
990
|
-
const
|
|
1063
|
+
const preservesAuthoredStop = tabResult.alignment === "end" && options?.allowEndTabOverflow === true || tabResult.explicit === true && authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE;
|
|
991
1064
|
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
992
1065
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
993
|
-
if (!
|
|
994
|
-
if (
|
|
1066
|
+
if (!preservesAuthoredStop && !landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
1067
|
+
if (preservesAuthoredStop) currentLine.availableWidth = Math.max(currentLine.availableWidth, currentLine.width + tabWidth + followingWidth);
|
|
995
1068
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
996
1069
|
startNewLine(runIndex, 0);
|
|
997
|
-
|
|
1070
|
+
updateMaxTabFont(style);
|
|
998
1071
|
}
|
|
999
1072
|
currentLine.width += tabWidth;
|
|
1000
1073
|
currentLine.trailingWhitespaceWidth = 0;
|
|
@@ -1098,7 +1171,13 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1098
1171
|
const nextBreak = wordBreaks[wordBreakIndex] ?? text.length;
|
|
1099
1172
|
const word = text.slice(charIndex, nextBreak);
|
|
1100
1173
|
const { measuredWord, wordWidth, fullWordWidth } = measureWordWithTrailingWhitespace(word, style);
|
|
1101
|
-
let leadingLetterSpacing = (
|
|
1174
|
+
let leadingLetterSpacing = precedingLetterSpacing({
|
|
1175
|
+
block,
|
|
1176
|
+
line: currentLine,
|
|
1177
|
+
runIndex,
|
|
1178
|
+
charIndex,
|
|
1179
|
+
runStyle: style
|
|
1180
|
+
});
|
|
1102
1181
|
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
1103
1182
|
const isFirstLine = lines.length === 0;
|
|
1104
1183
|
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
@@ -1223,7 +1302,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1223
1302
|
}
|
|
1224
1303
|
const wordTail = word.at(-1);
|
|
1225
1304
|
const protectedGlueWidth = protectedCrossRunGlueWidths[runIndex] ?? 0;
|
|
1226
|
-
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!
|
|
1305
|
+
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!isBreakBetween(wordTail, followingTextLeads[runIndex]) || protectedGlueWidth > 0) ? Math.max(trailingGlueWidths[runIndex] ?? 0, protectedGlueWidth) : 0;
|
|
1227
1306
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
1228
1307
|
const hangingAllowance = glueWidth === 0 ? hangingPunctuationWidth : 0;
|
|
1229
1308
|
const candidateFit = isJustifiedParagraph ? resolveTextCandidateFit({
|
|
@@ -23,6 +23,11 @@ type FontStyle = {
|
|
|
23
23
|
*/
|
|
24
24
|
eastAsiaFontFamily?: string;
|
|
25
25
|
eastAsiaAlternateFontFamily?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The run's effective `w:hint="eastAsia"`: the code points it moves measure
|
|
28
|
+
* with `eastAsiaFontFamily` too.
|
|
29
|
+
*/
|
|
30
|
+
eastAsiaHint?: boolean;
|
|
26
31
|
/**
|
|
27
32
|
* Complex-script font for Arabic, Hebrew, Indic and South-East Asian code
|
|
28
33
|
* points. Same contract as `eastAsiaFontFamily`, over a different slot.
|
|
@@ -50,6 +50,8 @@ type TabWidthResult = {
|
|
|
50
50
|
leader?: TabLeader;
|
|
51
51
|
/** Alignment that was applied */
|
|
52
52
|
alignment: TabAlignment | "default";
|
|
53
|
+
/** Set when the tab advanced to one of the context's explicit stops. */
|
|
54
|
+
explicit?: true;
|
|
53
55
|
};
|
|
54
56
|
/** Default tab interval: 720 twips = 0.5 inch */
|
|
55
57
|
declare const DEFAULT_TAB_INTERVAL_TWIPS = 720;
|
|
@@ -122,7 +122,8 @@ function calculateTabWidth(currentXPx, context, following = {}) {
|
|
|
122
122
|
return {
|
|
123
123
|
width,
|
|
124
124
|
...nextStop.leader !== void 0 ? { leader: nextStop.leader } : {},
|
|
125
|
-
alignment: nextStop.val
|
|
125
|
+
alignment: nextStop.val,
|
|
126
|
+
...context.explicitStops?.includes(nextStop) === true ? { explicit: true } : {}
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
@@ -22,6 +22,8 @@ type TableCellFloatingImage = {
|
|
|
22
22
|
distLeft: number;
|
|
23
23
|
distRight: number;
|
|
24
24
|
wrapText?: "bothSides" | "left" | "right" | "largest";
|
|
25
|
+
/** Authored wrap mode; decides whether and how the picture excludes text. */
|
|
26
|
+
wrapType?: ImageRun["wrapType"];
|
|
25
27
|
pmStart?: number;
|
|
26
28
|
pmEnd?: number;
|
|
27
29
|
};
|
|
@@ -53,6 +53,7 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
|
|
|
53
53
|
const distLeft = run.distLeft ?? 12;
|
|
54
54
|
const distRight = run.distRight ?? 12;
|
|
55
55
|
const verticalOriginY = run.position?.vertical?.relativeTo === "paragraph" ? placement.top : placement.contentTop;
|
|
56
|
+
if (run.layoutInCell === false && !resolvePosition) continue;
|
|
56
57
|
const resolved = run.layoutInCell === false && resolvePosition ? resolvePosition(run, verticalOriginY) : resolveCellScopedPosition({
|
|
57
58
|
run,
|
|
58
59
|
paragraphY: verticalOriginY,
|
|
@@ -82,6 +83,7 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
|
|
|
82
83
|
distLeft,
|
|
83
84
|
distRight,
|
|
84
85
|
wrapText,
|
|
86
|
+
...run.wrapType !== void 0 ? { wrapType: run.wrapType } : {},
|
|
85
87
|
...run.pmStart !== void 0 ? { pmStart: run.pmStart } : {},
|
|
86
88
|
...run.pmEnd !== void 0 ? { pmEnd: run.pmEnd } : {}
|
|
87
89
|
});
|
|
@@ -89,26 +91,56 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
|
|
|
89
91
|
}
|
|
90
92
|
return result;
|
|
91
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The text exclusion one cell-scoped picture casts, in cell content
|
|
96
|
+
* coordinates, or `undefined` when text ignores it.
|
|
97
|
+
*
|
|
98
|
+
* - `wp:wrapNone` (`behind` / `inFront`): text paints over or under the
|
|
99
|
+
* picture, so nothing is excluded.
|
|
100
|
+
* - `wp:wrapTopAndBottom`: no text beside the picture; lines it overlaps move
|
|
101
|
+
* below it.
|
|
102
|
+
* - `wp:wrapSquare` and the contour modes: text runs on the picture's open
|
|
103
|
+
* side. When the picture and its wrap distances cover the cell's content
|
|
104
|
+
* width there is no open side, and the text moves below the picture as for
|
|
105
|
+
* `wrapTopAndBottom`.
|
|
106
|
+
*/
|
|
107
|
+
function tableCellFloatingZone(img, contentWidth) {
|
|
108
|
+
if (img.wrapType === "behind" || img.wrapType === "inFront") return;
|
|
109
|
+
const rectLeft = img.x - img.distLeft;
|
|
110
|
+
const rectRight = img.x + img.width + img.distRight;
|
|
111
|
+
const topY = img.y - img.distTop;
|
|
112
|
+
const bottomY = img.y + img.height + img.distBottom;
|
|
113
|
+
const belowPicture = {
|
|
114
|
+
leftMargin: 0,
|
|
115
|
+
rightMargin: 0,
|
|
116
|
+
topY,
|
|
117
|
+
bottomY,
|
|
118
|
+
fullWidthBlock: true
|
|
119
|
+
};
|
|
120
|
+
if (img.wrapType === "topAndBottom") return belowPicture;
|
|
121
|
+
let leftMargin = 0;
|
|
122
|
+
let rightMargin = 0;
|
|
123
|
+
const wrapText = img.wrapText ?? "bothSides";
|
|
124
|
+
if (wrapText === "right") leftMargin = rectRight;
|
|
125
|
+
else if (wrapText === "left") rightMargin = contentWidth - rectLeft;
|
|
126
|
+
else if (img.side === "left") leftMargin = rectRight;
|
|
127
|
+
else rightMargin = contentWidth - rectLeft;
|
|
128
|
+
if (rectRight > 0 && rectLeft < contentWidth && Math.max(leftMargin, rightMargin) >= contentWidth) return belowPicture;
|
|
129
|
+
const clamped = clampFloatingWrapMargins(leftMargin, rightMargin, contentWidth);
|
|
130
|
+
return {
|
|
131
|
+
leftMargin: clamped.leftMargin,
|
|
132
|
+
rightMargin: clamped.rightMargin,
|
|
133
|
+
topY,
|
|
134
|
+
bottomY
|
|
135
|
+
};
|
|
136
|
+
}
|
|
92
137
|
function buildTableCellFloatingZones(floatingImages, contentWidth) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const wrapText = img.wrapText ?? "bothSides";
|
|
100
|
-
if (wrapText === "right") leftMargin = rectRight;
|
|
101
|
-
else if (wrapText === "left") rightMargin = contentWidth - (img.x - img.distLeft);
|
|
102
|
-
else if (img.side === "left") leftMargin = rectRight;
|
|
103
|
-
else rightMargin = contentWidth - (img.x - img.distLeft);
|
|
104
|
-
const clamped = clampFloatingWrapMargins(leftMargin, rightMargin, contentWidth);
|
|
105
|
-
return {
|
|
106
|
-
leftMargin: clamped.leftMargin,
|
|
107
|
-
rightMargin: clamped.rightMargin,
|
|
108
|
-
topY: rectTop,
|
|
109
|
-
bottomY: rectBottom
|
|
110
|
-
};
|
|
111
|
-
});
|
|
138
|
+
const zones = [];
|
|
139
|
+
for (const img of floatingImages) {
|
|
140
|
+
const zone = tableCellFloatingZone(img, contentWidth);
|
|
141
|
+
if (zone !== void 0) zones.push(zone);
|
|
142
|
+
}
|
|
143
|
+
return zones;
|
|
112
144
|
}
|
|
113
145
|
//#endregion
|
|
114
146
|
export { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
|
|
@@ -16,13 +16,38 @@ type BuildTableCellPlacementsOptions = {
|
|
|
16
16
|
grid: TableCellGrid;
|
|
17
17
|
columnWidths: readonly number[];
|
|
18
18
|
bidi: boolean;
|
|
19
|
+
/** `w:tblCellSpacing` in pixels; see {@link getTableCellSpacingInsetsX}. */
|
|
20
|
+
cellSpacing?: number | undefined;
|
|
19
21
|
};
|
|
22
|
+
type TableCellSpacingInsets = {
|
|
23
|
+
readonly start: number;
|
|
24
|
+
readonly end: number;
|
|
25
|
+
};
|
|
26
|
+
type TableCellSpacingInsetsY = {
|
|
27
|
+
readonly top: number;
|
|
28
|
+
readonly bottom: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Logical inline insets of a cell box inside its grid slot under
|
|
32
|
+
* `w:tblCellSpacing` (§17.4.43). The spacing separates every cell from its
|
|
33
|
+
* neighbours and from the table edge: each cell keeps one unit on each side of
|
|
34
|
+
* its slot, and a slot at the table's leading or trailing grid edge keeps one
|
|
35
|
+
* more for the edge itself.
|
|
36
|
+
*/
|
|
37
|
+
declare const getTableCellSpacingInsetsX: (cellSpacing: number | undefined, sourceColumn: number, columnSpan: number, columnCount: number) => TableCellSpacingInsets;
|
|
38
|
+
/**
|
|
39
|
+
* Block-direction insets of a cell box inside the rows it spans, the
|
|
40
|
+
* `w:tblCellSpacing` counterpart of {@link getTableCellSpacingInsetsX}: one
|
|
41
|
+
* unit above and below every cell, plus one more at the table's first and
|
|
42
|
+
* last rows.
|
|
43
|
+
*/
|
|
44
|
+
declare const getTableCellSpacingInsetsY: (cellSpacing: number | undefined, rowIndex: number, rowSpan: number, rowCount: number) => TableCellSpacingInsetsY;
|
|
20
45
|
declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
|
|
21
46
|
declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
|
|
22
47
|
declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
|
|
23
48
|
declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
|
|
24
49
|
/** Resolve all source cells to canonical logical columns and physical boxes. */
|
|
25
|
-
declare const buildTableCellPlacements: ({ grid, columnWidths, bidi }: BuildTableCellPlacementsOptions) => TableCellPlacements;
|
|
26
|
-
declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
|
|
50
|
+
declare const buildTableCellPlacements: ({ grid, columnWidths, bidi, cellSpacing }: BuildTableCellPlacementsOptions) => TableCellPlacements;
|
|
51
|
+
declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number, separated?: boolean) => number;
|
|
27
52
|
//#endregion
|
|
28
|
-
export { TableCellGrid, TableCellPlacement, TableCellPlacements, buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
53
|
+
export { TableCellGrid, TableCellPlacement, TableCellPlacements, TableCellSpacingInsets, TableCellSpacingInsetsY, buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight };
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { paintsCssBorder } from "../../utils/borderCss.js";
|
|
2
2
|
//#region src/layout-engine/measure/tableCellGrid.ts
|
|
3
|
+
const spacingUnit = (cellSpacing) => cellSpacing !== void 0 && Number.isFinite(cellSpacing) && cellSpacing > 0 ? cellSpacing : 0;
|
|
4
|
+
/**
|
|
5
|
+
* Logical inline insets of a cell box inside its grid slot under
|
|
6
|
+
* `w:tblCellSpacing` (§17.4.43). The spacing separates every cell from its
|
|
7
|
+
* neighbours and from the table edge: each cell keeps one unit on each side of
|
|
8
|
+
* its slot, and a slot at the table's leading or trailing grid edge keeps one
|
|
9
|
+
* more for the edge itself.
|
|
10
|
+
*/
|
|
11
|
+
const getTableCellSpacingInsetsX = (cellSpacing, sourceColumn, columnSpan, columnCount) => {
|
|
12
|
+
const unit = spacingUnit(cellSpacing);
|
|
13
|
+
if (unit === 0) return {
|
|
14
|
+
start: 0,
|
|
15
|
+
end: 0
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
start: sourceColumn <= 0 ? unit * 2 : unit,
|
|
19
|
+
end: sourceColumn + columnSpan >= columnCount ? unit * 2 : unit
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Block-direction insets of a cell box inside the rows it spans, the
|
|
24
|
+
* `w:tblCellSpacing` counterpart of {@link getTableCellSpacingInsetsX}: one
|
|
25
|
+
* unit above and below every cell, plus one more at the table's first and
|
|
26
|
+
* last rows.
|
|
27
|
+
*/
|
|
28
|
+
const getTableCellSpacingInsetsY = (cellSpacing, rowIndex, rowSpan, rowCount) => {
|
|
29
|
+
const unit = spacingUnit(cellSpacing);
|
|
30
|
+
if (unit === 0) return {
|
|
31
|
+
top: 0,
|
|
32
|
+
bottom: 0
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
top: rowIndex <= 0 ? unit * 2 : unit,
|
|
36
|
+
bottom: rowIndex + Math.max(1, rowSpan) >= rowCount ? unit * 2 : unit
|
|
37
|
+
};
|
|
38
|
+
};
|
|
3
39
|
const buildTableCellGrid = (rows, columnCount) => {
|
|
4
40
|
const occupiedColumnsByRow = /* @__PURE__ */ new Map();
|
|
5
41
|
const sourceCellsByRow = /* @__PURE__ */ new Map();
|
|
@@ -36,7 +72,7 @@ const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailab
|
|
|
36
72
|
const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
|
|
37
73
|
const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
|
|
38
74
|
/** Resolve all source cells to canonical logical columns and physical boxes. */
|
|
39
|
-
const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
|
|
75
|
+
const buildTableCellPlacements = ({ grid, columnWidths, bidi, cellSpacing }) => {
|
|
40
76
|
const columnOffsets = [0];
|
|
41
77
|
for (const columnWidth of columnWidths) columnOffsets.push((columnOffsets.at(-1) ?? 0) + columnWidth);
|
|
42
78
|
const tableWidth = columnOffsets.at(-1) ?? 0;
|
|
@@ -45,8 +81,11 @@ const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
|
|
|
45
81
|
if (sourceColumn < 0 || sourceColumn >= columnWidths.length) continue;
|
|
46
82
|
const declaredColumnSpan = Math.max(1, Math.trunc(cell.colSpan ?? 1));
|
|
47
83
|
const columnSpan = Math.min(declaredColumnSpan, columnWidths.length - sourceColumn);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
84
|
+
const slotLeft = columnOffsets.at(sourceColumn) ?? 0;
|
|
85
|
+
const slotRight = columnOffsets.at(sourceColumn + columnSpan) ?? slotLeft;
|
|
86
|
+
const insets = getTableCellSpacingInsetsX(cellSpacing, sourceColumn, columnSpan, columnWidths.length);
|
|
87
|
+
const logicalLeft = Math.min(slotRight, slotLeft + insets.start);
|
|
88
|
+
const logicalRight = Math.max(logicalLeft, slotRight - insets.end);
|
|
50
89
|
const width = logicalRight - logicalLeft;
|
|
51
90
|
placements.set(cell, {
|
|
52
91
|
sourceColumn,
|
|
@@ -57,7 +96,8 @@ const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
|
|
|
57
96
|
}
|
|
58
97
|
return placements;
|
|
59
98
|
};
|
|
60
|
-
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
|
|
99
|
+
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex, separated = false) => {
|
|
100
|
+
if (separated) return (cell?.borders?.top?.width ?? 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
61
101
|
const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
|
|
62
102
|
const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
|
|
63
103
|
const aboveOwnsEdge = aboveBottom !== void 0 && paintsCssBorder(aboveBottom.style);
|
|
@@ -83,4 +123,4 @@ const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
|
|
|
83
123
|
return occupiedColumns;
|
|
84
124
|
};
|
|
85
125
|
//#endregion
|
|
86
|
-
export { buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
126
|
+
export { buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight };
|