@stll/folio-core 0.47.7 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.js +565 -121
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/fontReadiness.js +3 -3
- package/dist/controller/hyphenationReadiness.d.ts +19 -0
- package/dist/controller/hyphenationReadiness.js +47 -0
- package/dist/controller/layoutPipeline.d.ts +3 -0
- package/dist/controller/layoutPipeline.js +70 -18
- package/dist/display-list/build/buildDisplayList.js +39 -12
- package/dist/display-list/build/pageFurniture.d.ts +7 -1
- package/dist/display-list/build/pageFurniture.js +21 -8
- package/dist/display-list/build/paragraphPrimitives.js +68 -11
- package/dist/display-list/build/tablePrimitives.js +10 -6
- package/dist/display-list/build/textBoxPrimitives.js +1 -1
- package/dist/display-list/build/unsupported.d.ts +2 -2
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
- package/dist/docx/attributeRemainder.js +10 -2
- package/dist/docx/blockContentParser.d.ts +2 -0
- package/dist/docx/blockContentParser.js +79 -64
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- package/dist/docx/commentParser.d.ts +2 -1
- package/dist/docx/commentParser.js +57 -45
- package/dist/docx/containerChildren.d.ts +21 -8
- package/dist/docx/containerChildren.js +39 -9
- package/dist/docx/documentParser.d.ts +5 -1
- package/dist/docx/documentParser.js +8 -5
- package/dist/docx/documentSectionFacts.d.ts +22 -0
- package/dist/docx/documentSectionFacts.js +43 -0
- package/dist/docx/drawingGroupChildren.d.ts +60 -0
- package/dist/docx/drawingGroupChildren.js +176 -0
- package/dist/docx/drawingUtils.js +3 -0
- package/dist/docx/fieldParser.d.ts +8 -1
- package/dist/docx/fieldParser.js +47 -3
- package/dist/docx/fontTableParser.js +34 -27
- package/dist/docx/footnoteParser.d.ts +3 -2
- package/dist/docx/footnoteParser.js +15 -12
- package/dist/docx/groupDrawingParser.d.ts +9 -2
- package/dist/docx/groupDrawingParser.js +125 -37
- package/dist/docx/headerFooterParser.d.ts +7 -2
- package/dist/docx/headerFooterParser.js +15 -5
- package/dist/docx/hyperlinkParser.d.ts +48 -36
- package/dist/docx/hyperlinkParser.js +97 -82
- package/dist/docx/numberingParser.js +108 -84
- package/dist/docx/packageParts.d.ts +45 -1
- package/dist/docx/packageParts.js +93 -2
- package/dist/docx/paraIdAttribute.d.ts +3 -2
- package/dist/docx/paragraphParser.d.ts +5 -1
- package/dist/docx/paragraphParser.js +468 -365
- package/dist/docx/paragraphProperties.js +123 -109
- package/dist/docx/paragraphPropertySource.d.ts +6 -1
- package/dist/docx/paragraphPropertySource.js +9 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
- package/dist/docx/parser.d.ts +7 -1
- package/dist/docx/parser.js +33 -16
- package/dist/docx/previewBudget.d.ts +55 -33
- package/dist/docx/previewBudget.js +59 -65
- package/dist/docx/rezip.js +41 -58
- package/dist/docx/runParser.d.ts +4 -1
- package/dist/docx/runParser.js +182 -184
- package/dist/docx/sdtProperties.js +87 -84
- package/dist/docx/sectionParser.js +169 -160
- package/dist/docx/selectiveSaveFlags.d.ts +10 -3
- package/dist/docx/selectiveSaveFlags.js +1 -1
- package/dist/docx/selectiveXmlPatch.d.ts +56 -34
- package/dist/docx/selectiveXmlPatch.js +222 -115
- package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
- package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
- package/dist/docx/serializer/paragraphSerializer.js +47 -42
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/serializer/settingsSerializer.js +19 -1
- package/dist/docx/settingsParser.js +8 -0
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/streamingXmlParser.d.ts +6 -2
- package/dist/docx/streamingXmlParser.js +12 -7
- package/dist/docx/styleParser.js +34 -8
- package/dist/docx/tableParser.d.ts +21 -12
- package/dist/docx/tableParser.js +518 -441
- package/dist/docx/textBoxParser.d.ts +18 -5
- package/dist/docx/textBoxParser.js +12 -6
- package/dist/docx/verbatimCapture.d.ts +10 -8
- package/dist/docx/verbatimCapture.js +45 -2
- package/dist/docx/vmlImageParser.d.ts +15 -13
- package/dist/docx/vmlImageParser.js +75 -60
- package/dist/docx/vmlPreview.d.ts +3 -3
- package/dist/docx/vmlPreview.js +3 -4
- package/dist/docx/xmlNamespaceContext.d.ts +9 -0
- package/dist/docx/xmlNamespaceContext.js +54 -0
- package/dist/docx/xmlParser.d.ts +6 -3
- package/dist/docx/xmlParser.js +11 -41
- package/dist/docx/xmlResourceLimits.d.ts +2 -1
- package/dist/docx/xmlResourceLimits.js +4 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/headless-layout.d.ts +11 -2
- package/dist/headless-layout.js +153 -107
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/internal/paragraphFormattingSerialization.js +3 -3
- package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
- package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
- package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
- package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
- package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
- package/dist/layout-bridge/engine/hitTest.js +5 -3
- package/dist/layout-bridge/engine/selectionRects.js +5 -3
- package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
- package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +102 -14
- package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
- package/dist/layout-engine/justificationCompatibility.js +9 -5
- package/dist/layout-engine/keep-together.d.ts +15 -6
- package/dist/layout-engine/keep-together.js +43 -16
- package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
- package/dist/layout-engine/layoutInstrumentation.js +8 -1
- package/dist/layout-engine/measure/advanceComposition.js +50 -7
- package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
- package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
- package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
- package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
- package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
- package/dist/layout-engine/measure/lineBreaks.js +10 -3
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
- package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
- package/dist/layout-engine/measure/measureBlocks.js +46 -10
- package/dist/layout-engine/measure/measureContainer.js +51 -8
- package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
- package/dist/layout-engine/measure/measureHelpers.js +34 -8
- package/dist/layout-engine/measure/measureParagraph.js +143 -101
- package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
- package/dist/layout-engine/measure/tabCalculator.js +2 -1
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +51 -19
- package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
- package/dist/layout-engine/measure/tableCellGrid.js +45 -5
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
- package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
- package/dist/layout-engine/noteAreaFlow.js +83 -0
- package/dist/layout-engine/paginator.d.ts +12 -1
- package/dist/layout-engine/paginator.js +18 -2
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +25 -2
- package/dist/layout-engine/types.d.ts +108 -3
- package/dist/layout-engine/types.js +96 -1
- package/dist/layout-painter/index.d.ts +3 -0
- package/dist/layout-painter/renderPage.js +32 -83
- package/dist/layout-painter/renderParagraph.d.ts +6 -1
- package/dist/layout-painter/renderParagraph.js +75 -26
- package/dist/layout-painter/renderTable.js +26 -13
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.d.ts +9 -9
- package/dist/prosemirror/attrs/index.js +133 -11
- package/dist/prosemirror/commands/comments.js +51 -41
- package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
- package/dist/prosemirror/contentControlRevisions.js +84 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
- package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
- package/dist/prosemirror/conversion/markInterner.js +90 -0
- package/dist/prosemirror/conversion/toProseDoc.js +117 -58
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
- package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +4 -2
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/paragraphDirection.d.ts +29 -2
- package/dist/prosemirror/paragraphDirection.js +21 -2
- package/dist/prosemirror/plugins/suggestionMode.js +37 -8
- package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
- package/dist/prosemirror/rejoinRunCarriers.js +66 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
- package/dist/prosemirror/schema/nodes.d.ts +50 -6
- package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
- package/dist/prosemirror/textBoxHostParagraph.js +21 -0
- package/dist/prosemirror/validation.js +91 -80
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/createDocument.js +10 -1
- package/dist/utils/scriptSegments.d.ts +33 -4
- package/dist/utils/scriptSegments.js +39 -6
- package/dist/utils/textFormattingMerge.d.ts +17 -3
- package/dist/utils/textFormattingMerge.js +18 -12
- package/dist/utils/trailingText.d.ts +22 -0
- package/dist/utils/trailingText.js +32 -0
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- package/dist/layout-engine/justifiedLineFit.js +0 -6
|
@@ -1,8 +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";
|
|
5
|
-
import {
|
|
4
|
+
import { hasCjk, hasComplexScript, hasEastAsiaSlotText } from "../../utils/scriptSegments.js";
|
|
5
|
+
import { splitTrailingToken } from "../../utils/trailingText.js";
|
|
6
6
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
7
7
|
import { isFloatingImageRun } from "../types.js";
|
|
8
8
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
@@ -10,9 +10,9 @@ import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./comp
|
|
|
10
10
|
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
11
11
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
12
12
|
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
13
|
-
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
13
|
+
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakBetween, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
14
14
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
15
|
-
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
15
|
+
import { DEFAULT_FONT_FAMILY, buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
16
16
|
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
17
17
|
import { calculateTabWidth, pixelsToTwips } from "./tabCalculator.js";
|
|
18
18
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
@@ -24,16 +24,14 @@ import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
|
24
24
|
* Converts runs into measured lines with typography metrics.
|
|
25
25
|
*/
|
|
26
26
|
const DEFAULT_FONT_SIZE = 11;
|
|
27
|
-
const DEFAULT_FONT_FAMILY = "Calibri";
|
|
28
27
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
29
28
|
const WIDTH_TOLERANCE = .5;
|
|
30
29
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
31
|
-
const JUSTIFY_SPACE_CONTRACTION_RATIO = .
|
|
32
|
-
const
|
|
33
|
-
const
|
|
30
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .25;
|
|
31
|
+
const JUSTIFY_STRETCH_PER_SHRINK = 1.7;
|
|
32
|
+
const JUSTIFY_ALWAYS_SHRINK_STRETCH = 1.5;
|
|
34
33
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
35
34
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
36
|
-
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
37
35
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
38
36
|
/**
|
|
39
37
|
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
@@ -85,9 +83,11 @@ function runToFontStyle(run) {
|
|
|
85
83
|
* keeps the base style so wrapping is untouched.
|
|
86
84
|
*/
|
|
87
85
|
function cjkLineHeightStyle(run, baseStyle) {
|
|
88
|
-
if (!run.text
|
|
86
|
+
if (!run.text) return baseStyle;
|
|
89
87
|
const eastAsia = baseStyle.eastAsiaFontFamily;
|
|
90
|
-
|
|
88
|
+
const eastAsiaFace = eastAsia !== void 0 && isCjkFont(eastAsia);
|
|
89
|
+
if (!hasCjk(run.text) && !(eastAsiaFace && hasEastAsiaSlotText(run.text, baseStyle.eastAsiaHint))) return baseStyle;
|
|
90
|
+
if (eastAsia !== void 0 && eastAsiaFace) {
|
|
91
91
|
const result = {
|
|
92
92
|
...baseStyle,
|
|
93
93
|
fontFamily: eastAsia
|
|
@@ -96,7 +96,7 @@ function cjkLineHeightStyle(run, baseStyle) {
|
|
|
96
96
|
if (baseStyle.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = baseStyle.eastAsiaAlternateFontFamily;
|
|
97
97
|
return result;
|
|
98
98
|
}
|
|
99
|
-
if (isCjkFont(baseStyle.fontFamily ??
|
|
99
|
+
if (isCjkFont(baseStyle.fontFamily ?? "Times New Roman")) return baseStyle;
|
|
100
100
|
return {
|
|
101
101
|
...baseStyle,
|
|
102
102
|
fontFamily: CJK_FALLBACK_FONT_FAMILY
|
|
@@ -153,7 +153,7 @@ const WORD_SINGLE_LINE_FLOOR = 1.15;
|
|
|
153
153
|
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
|
|
154
154
|
const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
|
|
155
155
|
fontSize,
|
|
156
|
-
fontFamily: fontFamily ??
|
|
156
|
+
fontFamily: fontFamily ?? "Times New Roman",
|
|
157
157
|
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
158
158
|
}));
|
|
159
159
|
const lineRule = spacing?.lineRule ?? "auto";
|
|
@@ -369,22 +369,6 @@ function uppercaseLetterRatio(text) {
|
|
|
369
369
|
}
|
|
370
370
|
function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
371
371
|
if (block.attrs?.justificationCompatibility?.type === "legacy") return { type: "rounding" };
|
|
372
|
-
if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
|
|
373
|
-
if (block.attrs?.listMarker !== void 0) {
|
|
374
|
-
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
375
|
-
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? {
|
|
376
|
-
type: "space",
|
|
377
|
-
ratio: JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO
|
|
378
|
-
} : {
|
|
379
|
-
type: "width",
|
|
380
|
-
ratio: JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO
|
|
381
|
-
};
|
|
382
|
-
return {
|
|
383
|
-
type: "space",
|
|
384
|
-
ratio: JUSTIFIED_FINAL_LINE_SPACE_CONTRACTION_RATIO,
|
|
385
|
-
maxWidthRatio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
372
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
389
373
|
if (isFirstLine && profile.hasTabRuns && (block.attrs?.indent?.hanging ?? 0) > 0) return {
|
|
390
374
|
type: "width",
|
|
@@ -407,19 +391,6 @@ function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
|
407
391
|
ratio: JUSTIFY_SPACE_CONTRACTION_RATIO
|
|
408
392
|
};
|
|
409
393
|
}
|
|
410
|
-
function resolveFinalLineJustifyFitStrategy(block, profile) {
|
|
411
|
-
if (supportsJustifiedFinalLineContraction(block)) return {
|
|
412
|
-
type: "space",
|
|
413
|
-
ratio: JUSTIFIED_FINAL_LINE_SPACE_CONTRACTION_RATIO,
|
|
414
|
-
maxWidthRatio: JUSTIFIED_FINAL_LINE_MAX_SHRINK_RATIO
|
|
415
|
-
};
|
|
416
|
-
return resolveJustifyFitStrategy(block, false, profile);
|
|
417
|
-
}
|
|
418
|
-
function resolveCandidateJustifyFitStrategy({ isFirstLine, isFinalCandidate, firstLineStrategy, continuationStrategy, finalLineStrategy }) {
|
|
419
|
-
if (isFirstLine) return firstLineStrategy;
|
|
420
|
-
if (isFinalCandidate) return finalLineStrategy;
|
|
421
|
-
return continuationStrategy;
|
|
422
|
-
}
|
|
423
394
|
const isIgnorableFinalTailRun = (run) => run.kind === "renderedPageBreak" || isTextRun(run) && run.text.length === 0;
|
|
424
395
|
function isFinalTextCandidate(block, runIndex, nextBreak) {
|
|
425
396
|
const currentRun = block.runs[runIndex];
|
|
@@ -431,27 +402,34 @@ function isFinalTextCandidate(block, runIndex, nextBreak) {
|
|
|
431
402
|
}
|
|
432
403
|
return true;
|
|
433
404
|
}
|
|
434
|
-
|
|
435
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Fit of a text candidate on a justified line: the paragraph's final word may
|
|
407
|
+
* be admitted by shrinking spaces, which then needs a paint plan.
|
|
408
|
+
*/
|
|
409
|
+
function resolveTextCandidateFit(isFinalCandidate, candidateWidth, availableWidth, tolerancePx) {
|
|
410
|
+
if (!isFinalCandidate) return {
|
|
436
411
|
type: "ordinary",
|
|
437
|
-
tolerancePx
|
|
412
|
+
tolerancePx
|
|
438
413
|
};
|
|
439
|
-
|
|
440
|
-
|
|
414
|
+
return admitFinalCandidate(candidateWidth, availableWidth, tolerancePx);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* A paragraph's last line is not stretched, so a final word admitted by space
|
|
418
|
+
* contraction needs an explicit paint plan; otherwise it would paint past the
|
|
419
|
+
* measure. Sub-pixel rounding overflow stays on the ordinary path.
|
|
420
|
+
*/
|
|
421
|
+
function admitFinalCandidate(candidateWidth, availableWidth, tolerancePx) {
|
|
422
|
+
const contractionPx = candidateWidth - availableWidth;
|
|
423
|
+
if (contractionPx <= WIDTH_TOLERANCE || contractionPx > tolerancePx) return {
|
|
441
424
|
type: "ordinary",
|
|
442
|
-
tolerancePx
|
|
443
|
-
};
|
|
444
|
-
const finalTolerancePx = justifyFitTolerance(line, finalStrategy, candidateSpaceWidth);
|
|
445
|
-
if (candidateWidth > line.availableWidth + finalTolerancePx) return {
|
|
446
|
-
type: "final-contraction-rejected",
|
|
447
|
-
tolerancePx: finalTolerancePx
|
|
425
|
+
tolerancePx
|
|
448
426
|
};
|
|
449
427
|
return {
|
|
450
428
|
type: "final-contraction-admitted",
|
|
451
|
-
tolerancePx
|
|
429
|
+
tolerancePx,
|
|
452
430
|
paint: {
|
|
453
431
|
type: "space-contraction",
|
|
454
|
-
contractionPx
|
|
432
|
+
contractionPx
|
|
455
433
|
}
|
|
456
434
|
};
|
|
457
435
|
}
|
|
@@ -459,18 +437,35 @@ function compressibleSpaceWidth(text, style) {
|
|
|
459
437
|
const count = countCompressibleSpaces(text);
|
|
460
438
|
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
461
439
|
}
|
|
462
|
-
function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
|
|
440
|
+
function justifyFitTolerance(line, strategy, candidateSpaceWidth, candidate) {
|
|
463
441
|
if (strategy.type === "rounding") return WIDTH_TOLERANCE;
|
|
464
442
|
if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return Math.max(WIDTH_TOLERANCE, boundedTolerance);
|
|
443
|
+
if (candidate !== void 0 && !prefersSpaceShrink(line, candidate)) return WIDTH_TOLERANCE;
|
|
444
|
+
return Math.max(WIDTH_TOLERANCE, (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio);
|
|
468
445
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
446
|
+
/**
|
|
447
|
+
* A justified line whose next word overflows the measure has two outcomes:
|
|
448
|
+
* keep the word and shrink the line's spaces, or wrap it and stretch the
|
|
449
|
+
* spaces left on the shorter line. Shrinking wins only when the stretch it
|
|
450
|
+
* avoids is large compared with the shrink it costs: the shrink factor may
|
|
451
|
+
* not exceed `1 + (stretch - 1) / JUSTIFY_STRETCH_PER_SHRINK`, and a stretch
|
|
452
|
+
* beyond `JUSTIFY_ALWAYS_SHRINK_STRETCH` always prefers shrinking. The space
|
|
453
|
+
* before the wrapped word hangs at the line end, so it does not stretch; a
|
|
454
|
+
* line left with nothing to stretch defers to the shrink budget alone.
|
|
455
|
+
*/
|
|
456
|
+
function prefersSpaceShrink(line, candidate) {
|
|
457
|
+
const overflow = candidate.width - line.availableWidth;
|
|
458
|
+
if (overflow <= WIDTH_TOLERANCE) return true;
|
|
459
|
+
const shrinkableSpaceWidth = line.regularSpaceWidth + candidate.innerSpaceWidth;
|
|
460
|
+
if (shrinkableSpaceWidth <= overflow) return false;
|
|
461
|
+
const hangingSpaceWidth = Math.min(line.trailingWhitespaceWidth, line.regularSpaceWidth);
|
|
462
|
+
const stretchableSpaceWidth = line.regularSpaceWidth - hangingSpaceWidth;
|
|
463
|
+
if (stretchableSpaceWidth <= 0) return true;
|
|
464
|
+
const visibleWidth = line.width - line.trailingWhitespaceWidth;
|
|
465
|
+
const stretch = 1 + Math.max(0, line.availableWidth - visibleWidth) / stretchableSpaceWidth;
|
|
466
|
+
if (stretch > JUSTIFY_ALWAYS_SHRINK_STRETCH) return true;
|
|
467
|
+
const shrink = shrinkableSpaceWidth / (shrinkableSpaceWidth - overflow);
|
|
468
|
+
return 1 + (stretch - 1) / JUSTIFY_STRETCH_PER_SHRINK >= shrink;
|
|
474
469
|
}
|
|
475
470
|
function trimTrailingSpacesAndTabs(text) {
|
|
476
471
|
let end = text.length;
|
|
@@ -503,6 +498,21 @@ function measureWordWithTrailingWhitespace(word, style) {
|
|
|
503
498
|
fullWordWidth: wordWidth + trailingWhitespaceWidth + boundarySpacing
|
|
504
499
|
};
|
|
505
500
|
}
|
|
501
|
+
const scaledLetterSpacing = (style) => (style.letterSpacing ?? 0) * getHorizontalScaleFactor(style.horizontalScale);
|
|
502
|
+
/**
|
|
503
|
+
* Character spacing still owed after the last character placed on the line,
|
|
504
|
+
* or 0 when the line has no preceding character at `charIndex` of `runIndex`.
|
|
505
|
+
* Within a run it is the run's own spacing; at a boundary between two text
|
|
506
|
+
* runs it is the preceding run's spacing on its final character.
|
|
507
|
+
*/
|
|
508
|
+
function precedingLetterSpacing({ block, line, runIndex, charIndex, runStyle }) {
|
|
509
|
+
if (line.fromRun === line.toRun && line.fromChar === line.toChar) return 0;
|
|
510
|
+
if (line.toRun === runIndex && line.toChar === charIndex) return scaledLetterSpacing(runStyle);
|
|
511
|
+
if (charIndex !== 0 || line.toRun !== runIndex - 1) return 0;
|
|
512
|
+
const previousRun = block.runs[runIndex - 1];
|
|
513
|
+
if (!previousRun || !isTextRun(previousRun) || previousRun.text.length === 0 || line.toChar !== previousRun.text.length) return 0;
|
|
514
|
+
return scaledLetterSpacing(runToFontStyle(previousRun));
|
|
515
|
+
}
|
|
506
516
|
function trailingCodePoint(text) {
|
|
507
517
|
if (text.length === 0) return;
|
|
508
518
|
const trailing = text.charCodeAt(text.length - 1);
|
|
@@ -548,6 +558,21 @@ function computeTrailingGlueWidths(block) {
|
|
|
548
558
|
}
|
|
549
559
|
return widths;
|
|
550
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* First character of the text that follows each run, looking through empty
|
|
563
|
+
* text runs; undefined before a non-text run or at the paragraph end.
|
|
564
|
+
*/
|
|
565
|
+
function computeFollowingTextLeads(block) {
|
|
566
|
+
const leads = Array.from({ length: block.runs.length }, () => void 0);
|
|
567
|
+
let lead;
|
|
568
|
+
for (let index = block.runs.length - 1; index >= 0; index--) {
|
|
569
|
+
leads[index] = lead;
|
|
570
|
+
const run = block.runs[index];
|
|
571
|
+
if (!run || !isTextRun(run)) lead = void 0;
|
|
572
|
+
else if (run.text) lead = run.text[0];
|
|
573
|
+
}
|
|
574
|
+
return leads;
|
|
575
|
+
}
|
|
551
576
|
function computeProtectedCrossRunGlueWidths(block) {
|
|
552
577
|
const widths = Array.from({ length: block.runs.length }, () => 0);
|
|
553
578
|
for (let index = 0; index < block.runs.length; index++) {
|
|
@@ -558,10 +583,10 @@ function computeProtectedCrossRunGlueWidths(block) {
|
|
|
558
583
|
run
|
|
559
584
|
}).provider;
|
|
560
585
|
if (!policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
561
|
-
const trailing =
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
let separator = trailing
|
|
586
|
+
const trailing = splitTrailingToken(run.text ?? "");
|
|
587
|
+
if (!trailing || trailing.token.length !== 1) continue;
|
|
588
|
+
const { token } = trailing;
|
|
589
|
+
let separator = trailing.separator;
|
|
565
590
|
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
566
591
|
let followingWord = "";
|
|
567
592
|
let unseparatedFollowingText = false;
|
|
@@ -720,7 +745,6 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
720
745
|
};
|
|
721
746
|
const firstLineJustifyFitStrategy = resolveJustifyFitStrategy(block, true, justificationProfile);
|
|
722
747
|
const continuationJustifyFitStrategy = resolveJustifyFitStrategy(block, false, justificationProfile);
|
|
723
|
-
const finalLineJustifyFitStrategy = resolveFinalLineJustifyFitStrategy(block, justificationProfile);
|
|
724
748
|
const floatingZones = options?.floatingZones;
|
|
725
749
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
726
750
|
const indent = attrs?.indent;
|
|
@@ -772,7 +796,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
772
796
|
};
|
|
773
797
|
}
|
|
774
798
|
if (runs.length === 0) {
|
|
775
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ??
|
|
799
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? "Times New Roman", attrs);
|
|
776
800
|
const outlineLineHeight = attrs?.reserveEmptyOutlineHeight ? emptyMetrics.lineHeight * 2 : emptyMetrics.lineHeight;
|
|
777
801
|
lines.push({
|
|
778
802
|
fromRun: 0,
|
|
@@ -793,7 +817,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
793
817
|
};
|
|
794
818
|
}
|
|
795
819
|
if (runs.length > 0 && runs.every((run) => isTextRun(run) && isEmptyTextRun(run))) {
|
|
796
|
-
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ??
|
|
820
|
+
const emptyMetrics = calculateEmptyParagraphMetrics(attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE, spacing, attrs?.defaultFontFamily ?? "Times New Roman", attrs);
|
|
797
821
|
lines.push({
|
|
798
822
|
fromRun: 0,
|
|
799
823
|
fromChar: 0,
|
|
@@ -813,6 +837,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
813
837
|
}
|
|
814
838
|
const trailingGlueWidths = computeTrailingGlueWidths(block);
|
|
815
839
|
const protectedCrossRunGlueWidths = computeProtectedCrossRunGlueWidths(block);
|
|
840
|
+
const followingTextLeads = computeFollowingTextLeads(block);
|
|
816
841
|
let currentLine = {
|
|
817
842
|
fromRun: 0,
|
|
818
843
|
fromChar: 0,
|
|
@@ -823,6 +848,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
823
848
|
regularSpaceWidth: 0,
|
|
824
849
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
825
850
|
maxFontMetrics: null,
|
|
851
|
+
maxTabFontSize: DEFAULT_FONT_SIZE,
|
|
852
|
+
maxTabFontMetrics: null,
|
|
826
853
|
maxImageHeightPx: 0,
|
|
827
854
|
maxExactImageHeightPx: 0,
|
|
828
855
|
maxMathHeightPx: 0,
|
|
@@ -833,9 +860,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
833
860
|
};
|
|
834
861
|
const calculateLineTypography = (line) => {
|
|
835
862
|
const paragraphFontSize = attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
836
|
-
const paragraphFontFamily = attrs?.defaultFontFamily ??
|
|
837
|
-
|
|
838
|
-
|
|
863
|
+
const paragraphFontFamily = attrs?.defaultFontFamily ?? "Times New Roman";
|
|
864
|
+
let lineFontMetrics = line.maxFontMetrics;
|
|
865
|
+
let fontSize = line.maxFontSize;
|
|
866
|
+
if (!lineFontMetrics) {
|
|
867
|
+
lineFontMetrics = line.maxTabFontMetrics;
|
|
868
|
+
fontSize = lineFontMetrics ? line.maxTabFontSize : paragraphFontSize;
|
|
869
|
+
}
|
|
870
|
+
const metrics = lineFontMetrics ?? getFontMetrics({
|
|
839
871
|
fontSize: paragraphFontSize,
|
|
840
872
|
fontFamily: paragraphFontFamily,
|
|
841
873
|
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
@@ -919,6 +951,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
919
951
|
regularSpaceWidth: 0,
|
|
920
952
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
921
953
|
maxFontMetrics: null,
|
|
954
|
+
maxTabFontSize: DEFAULT_FONT_SIZE,
|
|
955
|
+
maxTabFontMetrics: null,
|
|
922
956
|
maxImageHeightPx: 0,
|
|
923
957
|
maxExactImageHeightPx: 0,
|
|
924
958
|
maxMathHeightPx: 0,
|
|
@@ -942,6 +976,19 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
942
976
|
const metrics = getFontMetrics(style);
|
|
943
977
|
if (metrics.singleLineRatio > currentLine.maxFontMetrics.singleLineRatio) currentLine.maxFontMetrics = metrics;
|
|
944
978
|
};
|
|
979
|
+
/**
|
|
980
|
+
* Track the tallest tab on the line separately from content runs: a tab's
|
|
981
|
+
* font does not raise a line that carries other content, so a tab run
|
|
982
|
+
* formatted larger than its neighbouring text (a TOC entry whose tab keeps
|
|
983
|
+
* the paragraph-mark size) leaves the line at the text's height.
|
|
984
|
+
*/
|
|
985
|
+
const updateMaxTabFont = (style) => {
|
|
986
|
+
const fontSize = style.fontSize ?? DEFAULT_FONT_SIZE;
|
|
987
|
+
if (!currentLine.maxTabFontMetrics || fontSize > currentLine.maxTabFontSize) {
|
|
988
|
+
currentLine.maxTabFontSize = fontSize;
|
|
989
|
+
currentLine.maxTabFontMetrics = getFontMetrics(style);
|
|
990
|
+
}
|
|
991
|
+
};
|
|
945
992
|
let crossRunResume;
|
|
946
993
|
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
947
994
|
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
@@ -970,7 +1017,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
970
1017
|
}
|
|
971
1018
|
if (isTabRun(run)) {
|
|
972
1019
|
const style = runToFontStyle(run);
|
|
973
|
-
|
|
1020
|
+
updateMaxTabFont(style);
|
|
974
1021
|
const followingWidth = measureInlineWidthAfterTab(runs, runIndex, options?.fieldValues);
|
|
975
1022
|
const decimalPrefixWidth = measureDecimalPrefixWidthAfterTab(runs, runIndex, options?.fieldValues);
|
|
976
1023
|
const lineX = currentLine.width + currentLine.leftOffset;
|
|
@@ -987,14 +1034,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
987
1034
|
let tabWidth = tabResult.width;
|
|
988
1035
|
const authoredEndpoint = contentX + tabWidth + followingWidth;
|
|
989
1036
|
const activeContentRightEdge = maxWidth - currentLine.rightOffset;
|
|
990
|
-
const
|
|
1037
|
+
const preservesAuthoredStop = tabResult.alignment === "end" && options?.allowEndTabOverflow === true || tabResult.explicit === true && authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE;
|
|
991
1038
|
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
992
1039
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
993
|
-
if (!
|
|
994
|
-
if (
|
|
1040
|
+
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);
|
|
1041
|
+
if (preservesAuthoredStop) currentLine.availableWidth = Math.max(currentLine.availableWidth, currentLine.width + tabWidth + followingWidth);
|
|
995
1042
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
996
1043
|
startNewLine(runIndex, 0);
|
|
997
|
-
|
|
1044
|
+
updateMaxTabFont(style);
|
|
998
1045
|
}
|
|
999
1046
|
currentLine.width += tabWidth;
|
|
1000
1047
|
currentLine.trailingWhitespaceWidth = 0;
|
|
@@ -1039,7 +1086,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1039
1086
|
updateMaxFont(style);
|
|
1040
1087
|
const fieldWidth = measureTextWidth(fallback, style);
|
|
1041
1088
|
const fieldSpaceWidth = compressibleSpaceWidth(fallback, style);
|
|
1042
|
-
const fieldTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, lines.length === 0 ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, fieldSpaceWidth
|
|
1089
|
+
const fieldTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, lines.length === 0 ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, fieldSpaceWidth, {
|
|
1090
|
+
width: currentLine.width + fieldWidth,
|
|
1091
|
+
innerSpaceWidth: fieldSpaceWidth
|
|
1092
|
+
}) : WIDTH_TOLERANCE;
|
|
1043
1093
|
if (currentLine.width > 0 && currentLine.width + fieldWidth > currentLine.availableWidth + fieldTolerance) {
|
|
1044
1094
|
startNewLine(runIndex, 0);
|
|
1045
1095
|
updateMaxFont(style);
|
|
@@ -1098,18 +1148,20 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1098
1148
|
const nextBreak = wordBreaks[wordBreakIndex] ?? text.length;
|
|
1099
1149
|
const word = text.slice(charIndex, nextBreak);
|
|
1100
1150
|
const { measuredWord, wordWidth, fullWordWidth } = measureWordWithTrailingWhitespace(word, style);
|
|
1101
|
-
let leadingLetterSpacing = (
|
|
1151
|
+
let leadingLetterSpacing = precedingLetterSpacing({
|
|
1152
|
+
block,
|
|
1153
|
+
line: currentLine,
|
|
1154
|
+
runIndex,
|
|
1155
|
+
charIndex,
|
|
1156
|
+
runStyle: style
|
|
1157
|
+
});
|
|
1102
1158
|
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
1103
1159
|
const isFirstLine = lines.length === 0;
|
|
1104
1160
|
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
continuationStrategy: continuationJustifyFitStrategy,
|
|
1110
|
-
finalLineStrategy: finalLineJustifyFitStrategy
|
|
1111
|
-
});
|
|
1112
|
-
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, justifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
1161
|
+
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, isFirstLine ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, regularSpaceWidth, {
|
|
1162
|
+
width: currentLine.width + leadingLetterSpacing + wordWidth,
|
|
1163
|
+
innerSpaceWidth: 0
|
|
1164
|
+
}) : WIDTH_TOLERANCE;
|
|
1113
1165
|
const automaticHyphenation = effectiveLineBreakPolicy.automaticHyphenation;
|
|
1114
1166
|
const mayHyphenateLine = automaticHyphenation.type === "enabled" && (automaticHyphenation.consecutiveLineLimit === 0 || consecutiveHyphenatedLines < automaticHyphenation.consecutiveLineLimit) && exceedsHyphenationZone(currentLine, automaticHyphenation.hyphenationZoneTwips);
|
|
1115
1167
|
const isRunTail = nextBreak === text.length;
|
|
@@ -1223,20 +1275,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1223
1275
|
}
|
|
1224
1276
|
const wordTail = word.at(-1);
|
|
1225
1277
|
const protectedGlueWidth = protectedCrossRunGlueWidths[runIndex] ?? 0;
|
|
1226
|
-
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!
|
|
1278
|
+
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!isBreakBetween(wordTail, followingTextLeads[runIndex]) || protectedGlueWidth > 0) ? Math.max(trailingGlueWidths[runIndex] ?? 0, protectedGlueWidth) : 0;
|
|
1227
1279
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
1228
1280
|
const hangingAllowance = glueWidth === 0 ? hangingPunctuationWidth : 0;
|
|
1229
|
-
const candidateFit = isJustifiedParagraph ? resolveTextCandidateFit(
|
|
1230
|
-
block,
|
|
1231
|
-
line: currentLine,
|
|
1232
|
-
isFirstLine,
|
|
1233
|
-
isFinalCandidate: isFinalTextCandidate(block, runIndex, nextBreak),
|
|
1234
|
-
candidateWidth: currentLine.width + leadingLetterSpacing + wordWidth + glueWidth - hangingAllowance,
|
|
1235
|
-
candidateSpaceWidth: regularSpaceWidth,
|
|
1236
|
-
fallbackTolerancePx: widthTolerance,
|
|
1237
|
-
continuationStrategy: continuationJustifyFitStrategy,
|
|
1238
|
-
finalStrategy: finalLineJustifyFitStrategy
|
|
1239
|
-
}) : void 0;
|
|
1281
|
+
const candidateFit = isJustifiedParagraph ? resolveTextCandidateFit(isFinalTextCandidate(block, runIndex, nextBreak), currentLine.width + leadingLetterSpacing + wordWidth + glueWidth - hangingAllowance, currentLine.availableWidth, widthTolerance) : void 0;
|
|
1240
1282
|
const finalWrapTolerance = candidateFit?.tolerancePx ?? widthTolerance;
|
|
1241
1283
|
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + leadingLetterSpacing + wordWidth + glueWidth > currentLine.availableWidth + finalWrapTolerance + hangingAllowance) {
|
|
1242
1284
|
startNewLine(runIndex, charIndex);
|
|
@@ -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.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/smallCapsCasing.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Casing rules for synthesized `w:smallCaps` glyphs.
|
|
4
|
+
*
|
|
5
|
+
* A face that carries no `smcp`/`c2sc` substitution has no true small-capital
|
|
6
|
+
* glyph, so it is faked: a lowercase letter is drawn as its uppercase form at
|
|
7
|
+
* a smaller point size, on the run's own baseline, at a fixed fraction of the
|
|
8
|
+
* run's declared size — 0.8, an exact ratio observed to repeat across mixed
|
|
9
|
+
* two-size runs at every point size and never round to a half-point `w:sz`
|
|
10
|
+
* step. That is not a canvas or DOM backend's own `font-variant: small-caps`,
|
|
11
|
+
* which synthesizes at its own, uncontrollable, narrower ratio (Blink and
|
|
12
|
+
* WebKit use 0.7).
|
|
13
|
+
*
|
|
14
|
+
* Pure: no canvas, no DOM. Shared by every measurement and paint backend so a
|
|
15
|
+
* run's advances and its glyphs agree on which characters shrink.
|
|
16
|
+
*/
|
|
17
|
+
/** Fraction of the run's declared size a synthesized small capital draws at. */
|
|
18
|
+
declare const SMALL_CAPS_SCALE = 0.8;
|
|
19
|
+
/**
|
|
20
|
+
* Whether `char` is a cased letter in its lowercase form — the class
|
|
21
|
+
* `w:smallCaps` draws as a shrunken, uppercased capital. Digits, punctuation,
|
|
22
|
+
* symbols and already-uppercase letters keep the run's full size; combining
|
|
23
|
+
* `char`'s upper and lower forms this way (rather than comparing to a
|
|
24
|
+
* separate "is uppercase" test) is what correctly leaves caseless scripts
|
|
25
|
+
* (CJK, most punctuation) at full size too, since neither form differs from
|
|
26
|
+
* the character itself there.
|
|
27
|
+
*/
|
|
28
|
+
declare function isSmallCapsLetter(char: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Per-code-point small-caps classification for `text`, one entry per code
|
|
31
|
+
* point (iterated the same way `[...text]`/`for...of` does, so it lines up
|
|
32
|
+
* with any other code-point walk of the same string).
|
|
33
|
+
*
|
|
34
|
+
* Whitespace carries no case of its own; a run's space draws at the size of
|
|
35
|
+
* the character immediately before it — the trailing space after a
|
|
36
|
+
* synthesized word is itself synthesized — so a whitespace code point here
|
|
37
|
+
* takes the previous code point's class, and only falls back to full size
|
|
38
|
+
* when it opens the string (nothing to inherit from).
|
|
39
|
+
*/
|
|
40
|
+
declare function smallCapsMask(text: string): boolean[];
|
|
41
|
+
/** A maximal run of `text` drawn at one size: `small` selects which. */
|
|
42
|
+
type SmallCapsSegment = {
|
|
43
|
+
readonly text: string;
|
|
44
|
+
readonly small: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `text` split into maximal same-size segments for painting, each already
|
|
48
|
+
* uppercased where `small` — the shrunken capital a `smcp`-less face
|
|
49
|
+
* substitutes for the source lowercase letter. A run with no lowercase letter
|
|
50
|
+
* at all (already-uppercase headings, digits, punctuation) comes back as one
|
|
51
|
+
* `small: false` segment, so a caller can skip the extra painting work its
|
|
52
|
+
* mixed-size runs need.
|
|
53
|
+
*/
|
|
54
|
+
declare function smallCapsSegments(text: string): SmallCapsSegment[];
|
|
55
|
+
//#endregion
|
|
56
|
+
export { SMALL_CAPS_SCALE, SmallCapsSegment, isSmallCapsLetter, smallCapsMask, smallCapsSegments };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/smallCapsCasing.ts
|
|
2
|
+
/**
|
|
3
|
+
* Casing rules for synthesized `w:smallCaps` glyphs.
|
|
4
|
+
*
|
|
5
|
+
* A face that carries no `smcp`/`c2sc` substitution has no true small-capital
|
|
6
|
+
* glyph, so it is faked: a lowercase letter is drawn as its uppercase form at
|
|
7
|
+
* a smaller point size, on the run's own baseline, at a fixed fraction of the
|
|
8
|
+
* run's declared size — 0.8, an exact ratio observed to repeat across mixed
|
|
9
|
+
* two-size runs at every point size and never round to a half-point `w:sz`
|
|
10
|
+
* step. That is not a canvas or DOM backend's own `font-variant: small-caps`,
|
|
11
|
+
* which synthesizes at its own, uncontrollable, narrower ratio (Blink and
|
|
12
|
+
* WebKit use 0.7).
|
|
13
|
+
*
|
|
14
|
+
* Pure: no canvas, no DOM. Shared by every measurement and paint backend so a
|
|
15
|
+
* run's advances and its glyphs agree on which characters shrink.
|
|
16
|
+
*/
|
|
17
|
+
/** Fraction of the run's declared size a synthesized small capital draws at. */
|
|
18
|
+
const SMALL_CAPS_SCALE = .8;
|
|
19
|
+
/** Whitespace never gets its own small-caps class; see `smallCapsMask`. */
|
|
20
|
+
const WHITESPACE_PATTERN = /\s/u;
|
|
21
|
+
/**
|
|
22
|
+
* Whether `char` is a cased letter in its lowercase form — the class
|
|
23
|
+
* `w:smallCaps` draws as a shrunken, uppercased capital. Digits, punctuation,
|
|
24
|
+
* symbols and already-uppercase letters keep the run's full size; combining
|
|
25
|
+
* `char`'s upper and lower forms this way (rather than comparing to a
|
|
26
|
+
* separate "is uppercase" test) is what correctly leaves caseless scripts
|
|
27
|
+
* (CJK, most punctuation) at full size too, since neither form differs from
|
|
28
|
+
* the character itself there.
|
|
29
|
+
*/
|
|
30
|
+
function isSmallCapsLetter(char) {
|
|
31
|
+
return char.toLocaleUpperCase() !== char;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Per-code-point small-caps classification for `text`, one entry per code
|
|
35
|
+
* point (iterated the same way `[...text]`/`for...of` does, so it lines up
|
|
36
|
+
* with any other code-point walk of the same string).
|
|
37
|
+
*
|
|
38
|
+
* Whitespace carries no case of its own; a run's space draws at the size of
|
|
39
|
+
* the character immediately before it — the trailing space after a
|
|
40
|
+
* synthesized word is itself synthesized — so a whitespace code point here
|
|
41
|
+
* takes the previous code point's class, and only falls back to full size
|
|
42
|
+
* when it opens the string (nothing to inherit from).
|
|
43
|
+
*/
|
|
44
|
+
function smallCapsMask(text) {
|
|
45
|
+
const mask = [];
|
|
46
|
+
let previous = false;
|
|
47
|
+
for (const char of text) {
|
|
48
|
+
const small = WHITESPACE_PATTERN.test(char) ? previous : isSmallCapsLetter(char);
|
|
49
|
+
mask.push(small);
|
|
50
|
+
previous = small;
|
|
51
|
+
}
|
|
52
|
+
return mask;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `text` split into maximal same-size segments for painting, each already
|
|
56
|
+
* uppercased where `small` — the shrunken capital a `smcp`-less face
|
|
57
|
+
* substitutes for the source lowercase letter. A run with no lowercase letter
|
|
58
|
+
* at all (already-uppercase headings, digits, punctuation) comes back as one
|
|
59
|
+
* `small: false` segment, so a caller can skip the extra painting work its
|
|
60
|
+
* mixed-size runs need.
|
|
61
|
+
*/
|
|
62
|
+
function smallCapsSegments(text) {
|
|
63
|
+
const mask = smallCapsMask(text);
|
|
64
|
+
const segments = [];
|
|
65
|
+
let current = "";
|
|
66
|
+
let currentSmall;
|
|
67
|
+
let index = 0;
|
|
68
|
+
for (const char of text) {
|
|
69
|
+
const small = mask[index] ?? false;
|
|
70
|
+
if (currentSmall !== void 0 && small !== currentSmall) {
|
|
71
|
+
segments.push({
|
|
72
|
+
text: current,
|
|
73
|
+
small: currentSmall
|
|
74
|
+
});
|
|
75
|
+
current = "";
|
|
76
|
+
}
|
|
77
|
+
current += small ? char.toLocaleUpperCase() : char;
|
|
78
|
+
currentSmall = small;
|
|
79
|
+
index += 1;
|
|
80
|
+
}
|
|
81
|
+
if (current) segments.push({
|
|
82
|
+
text: current,
|
|
83
|
+
small: currentSmall ?? false
|
|
84
|
+
});
|
|
85
|
+
return segments;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { SMALL_CAPS_SCALE, isSmallCapsLetter, smallCapsMask, smallCapsSegments };
|
|
@@ -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
|
};
|