@stll/folio-core 0.29.0 → 0.31.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/folioEditor.d.ts +28 -1
- package/dist/controller/folioEditor.js +37 -26
- package/dist/controller/fontReadiness.js +28 -24
- package/dist/controller/hiddenEditorManager.d.ts +2 -7
- package/dist/controller/layoutPipeline.js +33 -3
- package/dist/controller/noteEditorManager.d.ts +3 -6
- package/dist/docx/metafileSvg.d.ts +12 -0
- package/dist/docx/metafileSvg.js +462 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
- package/dist/docx/parser.d.ts +2 -2
- package/dist/docx/parser.js +26 -0
- package/dist/docx/runParser.js +2 -1
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/server/createBilingualDocument.d.ts +20 -3
- package/dist/docx/server/createBilingualDocument.js +153 -22
- package/dist/docx/styleParser.js +2 -1
- package/dist/docx/textBoxParser.js +14 -0
- package/dist/docx/vmlImageParser.js +72 -152
- package/dist/docx/vmlPreview.d.ts +31 -0
- package/dist/docx/vmlPreview.js +535 -0
- package/dist/docx/wrapTypes.d.ts +2 -36
- package/dist/docx/wrapTypes.js +1 -39
- package/dist/fonts/fontAlternates.d.ts +8 -0
- package/dist/fonts/fontAlternates.js +16 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +78 -28
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
- package/dist/layout-bridge/engine/selectionRects.js +3 -1
- package/dist/layout-engine/index.js +65 -32
- package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
- package/dist/layout-engine/justifiedLineFit.js +6 -0
- package/dist/layout-engine/keep-together.js +15 -10
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
- package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
- package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
- package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
- package/dist/layout-engine/measure/measureBlocks.js +197 -30
- package/dist/layout-engine/measure/measureContainer.js +20 -24
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +14 -8
- package/dist/layout-engine/measure/measureParagraph.js +111 -37
- package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
- package/dist/layout-engine/measure/tabCalculator.js +144 -0
- package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
- package/dist/layout-engine/measure/tableCellFlow.js +45 -8
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
- package/dist/layout-engine/paginator.d.ts +26 -2
- package/dist/layout-engine/paginator.js +76 -14
- package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
- package/dist/layout-engine/paragraphSpacing.js +4 -3
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
- package/dist/layout-engine/section-breaks.d.ts +3 -1
- package/dist/layout-engine/section-breaks.js +6 -2
- package/dist/layout-engine/types.d.ts +18 -1
- package/dist/layout-engine/types.js +1 -1
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +169 -82
- package/dist/layout-painter/renderTable.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +18 -7
- package/dist/layout-painter/renderTextBox.d.ts +1 -1
- package/dist/layout-painter/renderTextBox.js +6 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
- package/dist/paged-layout/sectionBlockWidths.js +103 -14
- package/dist/prosemirror/attrs/index.js +12 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +6 -2
- package/dist/prosemirror/conversion/toProseDoc.js +10 -3
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
- package/dist/prosemirror/schema/nodes.d.ts +4 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
- package/dist/prosemirror/utils/tabCalculator.js +1 -140
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/types/documentEnumValues.d.ts +4 -1
- package/dist/types/documentEnumValues.js +12 -1
- package/dist/types/editor-story.d.ts +8 -0
- package/dist/types/editor-story.js +0 -0
- package/dist/types/remote-selection.d.ts +11 -0
- package/dist/types/remote-selection.js +0 -0
- package/dist/types/wrap.d.ts +36 -0
- package/dist/types/wrap.js +40 -0
- package/dist/utils/fontResolver.d.ts +1 -1
- package/dist/utils/fontResolver.js +42 -2
- package/dist/utils/formatToStyle.js +4 -2
- package/dist/utils/horizontalScale.d.ts +28 -0
- package/dist/utils/horizontalScale.js +42 -0
- package/package.json +2 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
2
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
|
+
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
3
3
|
import { isRtlParagraph } from "../../utils/paragraphBaseDirection.js";
|
|
4
4
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
5
5
|
import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
|
|
6
|
+
import { JUSTIFIED_LIST_FINAL_LINE_MAX_SHRINK_RATIO, JUSTIFIED_LIST_SPACE_CONTRACTION_RATIO, supportsJustifiedListFinalLineContraction } from "../justifiedLineFit.js";
|
|
6
7
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
7
8
|
import { isFloatingImageRun } from "../types.js";
|
|
8
9
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
9
|
-
import {
|
|
10
|
+
import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
|
|
10
11
|
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
11
12
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
12
13
|
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
@@ -14,6 +15,7 @@ import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar,
|
|
|
14
15
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
15
16
|
import { buildRunFontStyle, ptToPx, twipsToPx } from "./measureHelpers.js";
|
|
16
17
|
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
18
|
+
import { calculateTabWidth, pixelsToTwips } from "./tabCalculator.js";
|
|
17
19
|
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
18
20
|
//#region src/layout-engine/measure/measureParagraph.ts
|
|
19
21
|
/**
|
|
@@ -30,9 +32,6 @@ const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
|
30
32
|
const JUSTIFY_SPACE_CONTRACTION_RATIO = .075;
|
|
31
33
|
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
32
34
|
const JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO = .022;
|
|
33
|
-
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .32;
|
|
34
|
-
const JUSTIFY_LIST_CONTINUATION_MAX_SHRINK_TOLERANCE_RATIO = .015;
|
|
35
|
-
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
36
35
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
37
36
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
38
37
|
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
@@ -89,10 +88,15 @@ function runToFontStyle(run) {
|
|
|
89
88
|
function cjkLineHeightStyle(run, baseStyle) {
|
|
90
89
|
if (!run.text || !hasCjk(run.text)) return baseStyle;
|
|
91
90
|
const eastAsia = baseStyle.eastAsiaFontFamily;
|
|
92
|
-
if (eastAsia !== void 0 && isCjkFont(eastAsia))
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
if (eastAsia !== void 0 && isCjkFont(eastAsia)) {
|
|
92
|
+
const result = {
|
|
93
|
+
...baseStyle,
|
|
94
|
+
fontFamily: eastAsia
|
|
95
|
+
};
|
|
96
|
+
delete result.alternateFontFamily;
|
|
97
|
+
if (baseStyle.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = baseStyle.eastAsiaAlternateFontFamily;
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
96
100
|
if (isCjkFont(baseStyle.fontFamily ?? DEFAULT_FONT_FAMILY)) return baseStyle;
|
|
97
101
|
return {
|
|
98
102
|
...baseStyle,
|
|
@@ -102,10 +106,7 @@ function cjkLineHeightStyle(run, baseStyle) {
|
|
|
102
106
|
/** Pick the dominant typography metrics for a run containing complex script. */
|
|
103
107
|
function complexScriptLineHeightStyle(run, baseStyle) {
|
|
104
108
|
if (!hasComplexScriptFormatting(run) || !run.forceComplexScript && !hasComplexScript(run.text)) return baseStyle;
|
|
105
|
-
const complexStyle =
|
|
106
|
-
...baseStyle,
|
|
107
|
-
...resolveComplexScriptFormatting(run)
|
|
108
|
-
};
|
|
109
|
+
const complexStyle = applyComplexScriptFormatting(baseStyle, run);
|
|
109
110
|
return (complexStyle.fontSize ?? DEFAULT_FONT_SIZE) >= (baseStyle.fontSize ?? DEFAULT_FONT_SIZE) ? complexStyle : baseStyle;
|
|
110
111
|
}
|
|
111
112
|
/**
|
|
@@ -153,7 +154,8 @@ const WORD_SINGLE_LINE_FLOOR = 1.15;
|
|
|
153
154
|
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
|
|
154
155
|
const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
|
|
155
156
|
fontSize,
|
|
156
|
-
fontFamily: fontFamily ?? DEFAULT_FONT_FAMILY
|
|
157
|
+
fontFamily: fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
158
|
+
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
157
159
|
}));
|
|
158
160
|
const lineRule = spacing?.lineRule ?? "auto";
|
|
159
161
|
if (lineRule === "auto" || lineRule === "atLeast") {
|
|
@@ -378,15 +380,10 @@ function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
|
378
380
|
type: "width",
|
|
379
381
|
ratio: JUSTIFY_DEEP_HANGING_LIST_MARKER_SHRINK_TOLERANCE_RATIO
|
|
380
382
|
};
|
|
381
|
-
const left = block.attrs.indent?.left ?? 0;
|
|
382
|
-
if (hanging > 0 && left > hanging) return {
|
|
383
|
-
type: "width",
|
|
384
|
-
ratio: JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO
|
|
385
|
-
};
|
|
386
383
|
return {
|
|
387
384
|
type: "space",
|
|
388
|
-
ratio:
|
|
389
|
-
maxWidthRatio:
|
|
385
|
+
ratio: JUSTIFIED_LIST_SPACE_CONTRACTION_RATIO,
|
|
386
|
+
maxWidthRatio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
390
387
|
};
|
|
391
388
|
}
|
|
392
389
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
@@ -411,6 +408,54 @@ function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
|
411
408
|
ratio: JUSTIFY_SPACE_CONTRACTION_RATIO
|
|
412
409
|
};
|
|
413
410
|
}
|
|
411
|
+
function resolveFinalLineJustifyFitStrategy(block, profile) {
|
|
412
|
+
if (supportsJustifiedListFinalLineContraction(block)) return {
|
|
413
|
+
type: "space",
|
|
414
|
+
ratio: JUSTIFIED_LIST_SPACE_CONTRACTION_RATIO,
|
|
415
|
+
maxWidthRatio: JUSTIFIED_LIST_FINAL_LINE_MAX_SHRINK_RATIO
|
|
416
|
+
};
|
|
417
|
+
return resolveJustifyFitStrategy(block, false, profile);
|
|
418
|
+
}
|
|
419
|
+
function resolveCandidateJustifyFitStrategy({ isFirstLine, isFinalCandidate, firstLineStrategy, continuationStrategy, finalLineStrategy }) {
|
|
420
|
+
if (isFirstLine) return firstLineStrategy;
|
|
421
|
+
if (isFinalCandidate) return finalLineStrategy;
|
|
422
|
+
return continuationStrategy;
|
|
423
|
+
}
|
|
424
|
+
const isIgnorableFinalTailRun = (run) => run.kind === "renderedPageBreak" || isTextRun(run) && run.text.length === 0;
|
|
425
|
+
function isFinalTextCandidate(block, runIndex, nextBreak) {
|
|
426
|
+
const currentRun = block.runs[runIndex];
|
|
427
|
+
if (!currentRun || !isTextRun(currentRun) || nextBreak !== currentRun.text.length) return false;
|
|
428
|
+
for (let index = runIndex + 1; index < block.runs.length; index += 1) {
|
|
429
|
+
const run = block.runs[index];
|
|
430
|
+
if (run && isIgnorableFinalTailRun(run)) continue;
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
function resolveTextCandidateFit({ block, line, isFirstLine, isFinalCandidate, candidateWidth, candidateSpaceWidth, fallbackTolerancePx, continuationStrategy, finalStrategy }) {
|
|
436
|
+
if (isFirstLine || !isFinalCandidate || !supportsJustifiedListFinalLineContraction(block)) return {
|
|
437
|
+
type: "ordinary",
|
|
438
|
+
tolerancePx: fallbackTolerancePx
|
|
439
|
+
};
|
|
440
|
+
const ordinaryTolerancePx = justifyFitTolerance(line, continuationStrategy, candidateSpaceWidth);
|
|
441
|
+
if (candidateWidth <= line.availableWidth + ordinaryTolerancePx) return {
|
|
442
|
+
type: "ordinary",
|
|
443
|
+
tolerancePx: ordinaryTolerancePx
|
|
444
|
+
};
|
|
445
|
+
const finalTolerancePx = justifyFitTolerance(line, finalStrategy, candidateSpaceWidth);
|
|
446
|
+
if (candidateWidth > line.availableWidth + finalTolerancePx) return {
|
|
447
|
+
type: "final-contraction-rejected",
|
|
448
|
+
tolerancePx: finalTolerancePx
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
type: "final-contraction-admitted",
|
|
452
|
+
tolerancePx: finalTolerancePx,
|
|
453
|
+
paint: {
|
|
454
|
+
type: "space-contraction",
|
|
455
|
+
contractionPx: candidateWidth - line.availableWidth
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
414
459
|
function compressibleSpaceWidth(text, style) {
|
|
415
460
|
const count = countCompressibleSpaces(text);
|
|
416
461
|
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
@@ -453,7 +498,7 @@ function measureWordWithTrailingWhitespace(word, style) {
|
|
|
453
498
|
fullWordWidth: measureTextWidth(word, style)
|
|
454
499
|
};
|
|
455
500
|
const trailingWhitespaceWidth = measureTextWidth(trailingWhitespace, style);
|
|
456
|
-
const boundarySpacing = measuredWord.length > 0 ? (style.letterSpacing ?? 0) * (
|
|
501
|
+
const boundarySpacing = measuredWord.length > 0 ? (style.letterSpacing ?? 0) * getHorizontalScaleFactor(style.horizontalScale) : 0;
|
|
457
502
|
return {
|
|
458
503
|
measuredWord,
|
|
459
504
|
wordWidth,
|
|
@@ -678,6 +723,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
678
723
|
};
|
|
679
724
|
const firstLineJustifyFitStrategy = resolveJustifyFitStrategy(block, true, justificationProfile);
|
|
680
725
|
const continuationJustifyFitStrategy = resolveJustifyFitStrategy(block, false, justificationProfile);
|
|
726
|
+
const finalLineJustifyFitStrategy = resolveFinalLineJustifyFitStrategy(block, justificationProfile);
|
|
681
727
|
const floatingZones = options?.floatingZones;
|
|
682
728
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
683
729
|
const indent = attrs?.indent;
|
|
@@ -792,7 +838,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
792
838
|
const fontSize = line.maxFontMetrics ? line.maxFontSize : paragraphFontSize;
|
|
793
839
|
const metrics = line.maxFontMetrics ?? getFontMetrics({
|
|
794
840
|
fontSize: paragraphFontSize,
|
|
795
|
-
fontFamily: paragraphFontFamily
|
|
841
|
+
fontFamily: paragraphFontFamily,
|
|
842
|
+
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
796
843
|
});
|
|
797
844
|
const finalTypography = { ...calculateTypographyMetrics(fontSize, spacing, metrics) };
|
|
798
845
|
const inlineObjectHeight = Math.max(line.maxImageHeightPx, line.maxMathHeightPx);
|
|
@@ -840,6 +887,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
840
887
|
toChar: currentLine.toChar,
|
|
841
888
|
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
842
889
|
...finalTypography,
|
|
890
|
+
...currentLine.justificationPaint !== void 0 ? { justificationPaint: currentLine.justificationPaint } : {},
|
|
843
891
|
...currentLine.discretionaryHyphen ? { discretionaryHyphen: currentLine.discretionaryHyphen } : {},
|
|
844
892
|
...currentLine.renderedPageBreakBefore ? { renderedPageBreakBefore: true } : {}
|
|
845
893
|
};
|
|
@@ -900,7 +948,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
900
948
|
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
901
949
|
const run = runs[runIndex];
|
|
902
950
|
if (run.kind === "renderedPageBreak") {
|
|
903
|
-
if (!runs.slice(runIndex + 1).some((followingRun) => followingRun
|
|
951
|
+
if (!runs.slice(runIndex + 1).some((followingRun) => !isIgnorableFinalTailRun(followingRun))) {
|
|
904
952
|
currentLine.toRun = runIndex;
|
|
905
953
|
currentLine.toChar = 0;
|
|
906
954
|
continue;
|
|
@@ -1051,10 +1099,18 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1051
1099
|
const nextBreak = wordBreaks[wordBreakIndex] ?? text.length;
|
|
1052
1100
|
const word = text.slice(charIndex, nextBreak);
|
|
1053
1101
|
const { measuredWord, wordWidth, fullWordWidth } = measureWordWithTrailingWhitespace(word, style);
|
|
1102
|
+
let leadingLetterSpacing = (currentLine.fromRun !== currentLine.toRun || currentLine.fromChar !== currentLine.toChar) && currentLine.toRun === runIndex && currentLine.toChar === charIndex ? (style.letterSpacing ?? 0) * ((style.horizontalScale ?? 100) / 100) : 0;
|
|
1054
1103
|
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
1055
1104
|
const isFirstLine = lines.length === 0;
|
|
1056
1105
|
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
1057
|
-
const
|
|
1106
|
+
const justifyFitStrategy = resolveCandidateJustifyFitStrategy({
|
|
1107
|
+
isFirstLine,
|
|
1108
|
+
isFinalCandidate: isFinalTextCandidate(block, runIndex, nextBreak),
|
|
1109
|
+
firstLineStrategy: firstLineJustifyFitStrategy,
|
|
1110
|
+
continuationStrategy: continuationJustifyFitStrategy,
|
|
1111
|
+
finalLineStrategy: finalLineJustifyFitStrategy
|
|
1112
|
+
});
|
|
1113
|
+
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, justifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
1058
1114
|
const automaticHyphenation = effectiveLineBreakPolicy.automaticHyphenation;
|
|
1059
1115
|
const mayHyphenateLine = automaticHyphenation.type === "enabled" && (automaticHyphenation.consecutiveLineLimit === 0 || consecutiveHyphenatedLines < automaticHyphenation.consecutiveLineLimit) && exceedsHyphenationZone(currentLine, automaticHyphenation.hyphenationZoneTwips);
|
|
1060
1116
|
const isRunTail = nextBreak === text.length;
|
|
@@ -1073,10 +1129,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1073
1129
|
breaks: findHyphenationBreaks(fullWordText, breakPolicy)
|
|
1074
1130
|
};
|
|
1075
1131
|
}
|
|
1076
|
-
const overflowsCurrentLine = wordWidth > 0 && currentLine.width + wordWidth > currentLine.availableWidth + widthTolerance;
|
|
1132
|
+
const overflowsCurrentLine = wordWidth > 0 && currentLine.width + leadingLetterSpacing + wordWidth > currentLine.availableWidth + widthTolerance;
|
|
1077
1133
|
if (mayHyphenateLine && overflowsCurrentLine && activeHyphenationWord) {
|
|
1078
1134
|
const consumed = charIndex - activeHyphenationWord.start;
|
|
1079
|
-
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
1135
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width - leadingLetterSpacing + widthTolerance;
|
|
1080
1136
|
const hyphenWidth = measureTextWidth("-", style);
|
|
1081
1137
|
let fittingBreak;
|
|
1082
1138
|
for (const breakOffset of activeHyphenationWord.breaks) {
|
|
@@ -1086,7 +1142,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1086
1142
|
if (fittingBreak !== void 0) {
|
|
1087
1143
|
const absoluteBreak = activeHyphenationWord.start + fittingBreak;
|
|
1088
1144
|
const prefix = text.slice(charIndex, absoluteBreak);
|
|
1089
|
-
currentLine.width += measureTextWidth(prefix, style) + hyphenWidth;
|
|
1145
|
+
currentLine.width += leadingLetterSpacing + measureTextWidth(prefix, style) + hyphenWidth;
|
|
1090
1146
|
currentLine.trailingWhitespaceWidth = 0;
|
|
1091
1147
|
currentLine.toRun = runIndex;
|
|
1092
1148
|
currentLine.toChar = absoluteBreak;
|
|
@@ -1097,8 +1153,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1097
1153
|
continue;
|
|
1098
1154
|
}
|
|
1099
1155
|
}
|
|
1100
|
-
if (crossRunWord && currentLine.width + crossRunWord.width > currentLine.availableWidth + widthTolerance) {
|
|
1101
|
-
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
1156
|
+
if (crossRunWord && currentLine.width + leadingLetterSpacing + crossRunWord.width > currentLine.availableWidth + widthTolerance) {
|
|
1157
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width - leadingLetterSpacing + widthTolerance;
|
|
1102
1158
|
let fittingPrefix;
|
|
1103
1159
|
for (let breakIndex = crossRunWord.breaks.length - 1; breakIndex >= 0; breakIndex -= 1) {
|
|
1104
1160
|
const breakOffset = crossRunWord.breaks[breakIndex];
|
|
@@ -1112,7 +1168,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1112
1168
|
}
|
|
1113
1169
|
if (fittingPrefix) {
|
|
1114
1170
|
for (const segment of fittingPrefix.segments) updateMaxFont(cjkLineHeightStyle(segment.run, complexScriptLineHeightStyle(segment.run, segment.style)));
|
|
1115
|
-
currentLine.width += fittingPrefix.width + measureTextWidth("-", fittingPrefix.hyphenStyle);
|
|
1171
|
+
currentLine.width += leadingLetterSpacing + fittingPrefix.width + measureTextWidth("-", fittingPrefix.hyphenStyle);
|
|
1116
1172
|
currentLine.trailingWhitespaceWidth = 0;
|
|
1117
1173
|
currentLine.toRun = fittingPrefix.endRunIndex;
|
|
1118
1174
|
currentLine.toChar = fittingPrefix.endChar;
|
|
@@ -1133,18 +1189,21 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1133
1189
|
}
|
|
1134
1190
|
if (wordWidth - hangingPunctuationWidth > currentLine.availableWidth + widthTolerance) {
|
|
1135
1191
|
let chunkStart = 0;
|
|
1192
|
+
let chunkLeadingLetterSpacing = leadingLetterSpacing;
|
|
1136
1193
|
while (chunkStart < measuredWord.length) {
|
|
1137
|
-
const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
|
|
1194
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width - chunkLeadingLetterSpacing + WIDTH_TOLERANCE;
|
|
1138
1195
|
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft, currentLine.width === 0, breakPolicy);
|
|
1139
1196
|
if (bestEnd === 0) {
|
|
1140
1197
|
startNewLine(runIndex, charIndex + chunkStart);
|
|
1198
|
+
chunkLeadingLetterSpacing = 0;
|
|
1141
1199
|
updateMaxFont(lineHeightStyle);
|
|
1142
1200
|
continue;
|
|
1143
1201
|
}
|
|
1144
1202
|
const chunkEnd = chunkStart + bestEnd;
|
|
1145
1203
|
const chunk = measuredWord.slice(chunkStart, chunkEnd);
|
|
1146
1204
|
const chunkWidth = measureTextWidth(chunk, style);
|
|
1147
|
-
currentLine.width += chunkWidth;
|
|
1205
|
+
currentLine.width += chunkLeadingLetterSpacing + chunkWidth;
|
|
1206
|
+
chunkLeadingLetterSpacing = 0;
|
|
1148
1207
|
currentLine.trailingWhitespaceWidth = chunkWidth - measureTextWidth(trimTrailingSpacesAndTabs(chunk), style);
|
|
1149
1208
|
currentLine.regularSpaceWidth += compressibleSpaceWidth(chunk, style);
|
|
1150
1209
|
currentLine.toRun = runIndex;
|
|
@@ -1168,13 +1227,27 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1168
1227
|
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!isBreakChar(wordTail) || protectedGlueWidth > 0) ? Math.max(trailingGlueWidths[runIndex] ?? 0, protectedGlueWidth) : 0;
|
|
1169
1228
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
1170
1229
|
const hangingAllowance = glueWidth === 0 ? hangingPunctuationWidth : 0;
|
|
1171
|
-
|
|
1230
|
+
const candidateFit = isJustifiedParagraph ? resolveTextCandidateFit({
|
|
1231
|
+
block,
|
|
1232
|
+
line: currentLine,
|
|
1233
|
+
isFirstLine,
|
|
1234
|
+
isFinalCandidate: isFinalTextCandidate(block, runIndex, nextBreak),
|
|
1235
|
+
candidateWidth: currentLine.width + leadingLetterSpacing + wordWidth + glueWidth - hangingAllowance,
|
|
1236
|
+
candidateSpaceWidth: regularSpaceWidth,
|
|
1237
|
+
fallbackTolerancePx: widthTolerance,
|
|
1238
|
+
continuationStrategy: continuationJustifyFitStrategy,
|
|
1239
|
+
finalStrategy: finalLineJustifyFitStrategy
|
|
1240
|
+
}) : void 0;
|
|
1241
|
+
const finalWrapTolerance = candidateFit?.tolerancePx ?? widthTolerance;
|
|
1242
|
+
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + leadingLetterSpacing + wordWidth + glueWidth > currentLine.availableWidth + finalWrapTolerance + hangingAllowance) {
|
|
1172
1243
|
startNewLine(runIndex, charIndex);
|
|
1244
|
+
leadingLetterSpacing = 0;
|
|
1173
1245
|
updateMaxFont(lineHeightStyle);
|
|
1174
1246
|
}
|
|
1175
|
-
currentLine.
|
|
1247
|
+
if (candidateFit?.type === "final-contraction-admitted") currentLine.justificationPaint = candidateFit.paint;
|
|
1248
|
+
currentLine.width += leadingLetterSpacing + fullWordWidth;
|
|
1176
1249
|
const wordTrailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
1177
|
-
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
1250
|
+
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + leadingLetterSpacing + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
1178
1251
|
currentLine.regularSpaceWidth += compressibleSpaceWidth(word, style);
|
|
1179
1252
|
currentLine.toRun = runIndex;
|
|
1180
1253
|
currentLine.toChar = nextBreak;
|
|
@@ -1187,7 +1260,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
1187
1260
|
const fontFamily = currentLine.maxFontMetrics?.fontFamily ?? attrs?.defaultFontFamily;
|
|
1188
1261
|
updateMaxFont({
|
|
1189
1262
|
fontSize: listParagraphMarkFontSize,
|
|
1190
|
-
...fontFamily === void 0 ? {} : { fontFamily }
|
|
1263
|
+
...fontFamily === void 0 ? {} : { fontFamily },
|
|
1264
|
+
...currentLine.maxFontMetrics === null && attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
1191
1265
|
});
|
|
1192
1266
|
}
|
|
1193
1267
|
finalizeLine();
|
|
@@ -14,6 +14,7 @@ declare namespace measureTypes_d_exports {
|
|
|
14
14
|
*/
|
|
15
15
|
type FontStyle = {
|
|
16
16
|
fontFamily?: string;
|
|
17
|
+
alternateFontFamily?: string;
|
|
17
18
|
/**
|
|
18
19
|
* East-Asian font for CJK code points. When set, `measureTextWidth` /
|
|
19
20
|
* `measureRun` measure CJK code points with this font and the rest with
|
|
@@ -21,11 +22,13 @@ type FontStyle = {
|
|
|
21
22
|
* and click positioning stay in sync.
|
|
22
23
|
*/
|
|
23
24
|
eastAsiaFontFamily?: string;
|
|
25
|
+
eastAsiaAlternateFontFamily?: string;
|
|
24
26
|
/**
|
|
25
27
|
* Complex-script font for Arabic, Hebrew, Indic and South-East Asian code
|
|
26
28
|
* points. Same contract as `eastAsiaFontFamily`, over a different slot.
|
|
27
29
|
*/
|
|
28
30
|
complexScriptFontFamily?: string;
|
|
31
|
+
complexScriptAlternateFontFamily?: string;
|
|
29
32
|
complexScriptFontSize?: number;
|
|
30
33
|
complexScriptBold?: boolean;
|
|
31
34
|
complexScriptItalic?: boolean;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/tabCalculator.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tab Width Calculator
|
|
4
|
+
*
|
|
5
|
+
* Computes tab widths based on position and tab stops.
|
|
6
|
+
* Follows OOXML tab stop semantics:
|
|
7
|
+
* - Default tab interval: 720 twips (0.5 inch, 48px at 96dpi)
|
|
8
|
+
* - Tab stops can be start (left), end (right), center, decimal, or bar
|
|
9
|
+
* - Explicit tab stops override default stops
|
|
10
|
+
*
|
|
11
|
+
* Based on ECMA-376 specification and clean-room understanding of tab layout.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tab alignment types
|
|
15
|
+
*/
|
|
16
|
+
type TabAlignment = "start" | "end" | "center" | "decimal" | "bar" | "clear";
|
|
17
|
+
/**
|
|
18
|
+
* Tab leader (fill character) types
|
|
19
|
+
*/
|
|
20
|
+
type TabLeader = "none" | "dot" | "hyphen" | "underscore" | "middleDot" | "heavy";
|
|
21
|
+
/**
|
|
22
|
+
* Tab stop definition
|
|
23
|
+
*/
|
|
24
|
+
type TabStop = {
|
|
25
|
+
/** Tab alignment mode */
|
|
26
|
+
val: TabAlignment;
|
|
27
|
+
/** Position in twips from left margin */
|
|
28
|
+
pos: number;
|
|
29
|
+
/** Optional leader character */
|
|
30
|
+
leader?: TabLeader;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Context for tab calculations
|
|
34
|
+
*/
|
|
35
|
+
type TabContext = {
|
|
36
|
+
/** Explicit tab stops from paragraph or style */
|
|
37
|
+
explicitStops?: TabStop[];
|
|
38
|
+
/** Default tab interval in twips (default: 720 = 0.5 inch) */
|
|
39
|
+
defaultTabInterval?: number;
|
|
40
|
+
/** Left indent in twips */
|
|
41
|
+
leftIndent?: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Result of tab width calculation
|
|
45
|
+
*/
|
|
46
|
+
type TabWidthResult = {
|
|
47
|
+
/** Width of the tab in pixels */
|
|
48
|
+
width: number;
|
|
49
|
+
/** Leader character to render (if any) */
|
|
50
|
+
leader?: TabLeader;
|
|
51
|
+
/** Alignment that was applied */
|
|
52
|
+
alignment: TabAlignment | "default";
|
|
53
|
+
};
|
|
54
|
+
/** Default tab interval: 720 twips = 0.5 inch */
|
|
55
|
+
declare const DEFAULT_TAB_INTERVAL_TWIPS = 720;
|
|
56
|
+
/** Twips per inch */
|
|
57
|
+
declare const TWIPS_PER_INCH = 1440;
|
|
58
|
+
/** Pixels per inch (96 dpi standard for CSS) */
|
|
59
|
+
declare const PIXELS_PER_INCH = 96;
|
|
60
|
+
/**
|
|
61
|
+
* Convert twips to pixels
|
|
62
|
+
* @param twips - Value in twips (1/1440 inch)
|
|
63
|
+
* @returns Value in pixels (at 96 dpi)
|
|
64
|
+
*/
|
|
65
|
+
declare function twipsToPixels(twips: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* Convert pixels to twips
|
|
68
|
+
* @param pixels - Value in pixels (at 96 dpi)
|
|
69
|
+
* @returns Value in twips (1/1440 inch)
|
|
70
|
+
*/
|
|
71
|
+
declare function pixelsToTwips(pixels: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* Compute the list of effective tab stops for a paragraph
|
|
74
|
+
*
|
|
75
|
+
* Merges explicit stops with default stops at regular intervals.
|
|
76
|
+
* Explicit stops before the left indent remain available to a hanging first
|
|
77
|
+
* line. Continuation lines naturally skip them because their cursor already
|
|
78
|
+
* starts at the left indent.
|
|
79
|
+
*
|
|
80
|
+
* @param context - Tab context with explicit stops and settings
|
|
81
|
+
* @returns Sorted array of tab stops in twips
|
|
82
|
+
*/
|
|
83
|
+
declare function computeTabStops(context: TabContext): TabStop[];
|
|
84
|
+
/**
|
|
85
|
+
* Pixel widths of the content after a tab, needed to position non-left stops.
|
|
86
|
+
*
|
|
87
|
+
* Caller measures its own runs (the painter and the layout measurer have
|
|
88
|
+
* different run models and measurement paths) and passes the result here, so
|
|
89
|
+
* this module owns the stop grid + alignment math without owning measurement.
|
|
90
|
+
*/
|
|
91
|
+
type TabFollowingContent = {
|
|
92
|
+
/** Total width of the runs after the tab. Used for `end` and `center` stops. */
|
|
93
|
+
followingWidth?: number;
|
|
94
|
+
/** Width of the content before the decimal separator. Used for `decimal` stops. */
|
|
95
|
+
decimalPrefixWidth?: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Calculate the width of a tab character.
|
|
99
|
+
*
|
|
100
|
+
* Finds the next tab stop after the current position and computes the width
|
|
101
|
+
* needed to reach it. For `end`/`center`/`decimal` stops the content after the
|
|
102
|
+
* tab is anchored against the stop, so the caller supplies its measured width
|
|
103
|
+
* via {@link TabFollowingContent}.
|
|
104
|
+
*
|
|
105
|
+
* @param currentXPx - Current horizontal position in pixels (from the content
|
|
106
|
+
* area's left edge — the same origin tab stop positions are measured from)
|
|
107
|
+
* @param context - Tab context with stops and settings
|
|
108
|
+
* @param following - Measured widths of the content after the tab
|
|
109
|
+
* @returns Tab width result with width in pixels
|
|
110
|
+
*/
|
|
111
|
+
declare function calculateTabWidth(currentXPx: number, context: TabContext, following?: TabFollowingContent): TabWidthResult;
|
|
112
|
+
/**
|
|
113
|
+
* Calculate tab width with simple default stops
|
|
114
|
+
*
|
|
115
|
+
* Simplified version for basic tab rendering without explicit stops.
|
|
116
|
+
* Uses the default 0.5 inch (48px) tab interval.
|
|
117
|
+
*
|
|
118
|
+
* @param currentXPx - Current horizontal position in pixels
|
|
119
|
+
* @returns Width of the tab in pixels
|
|
120
|
+
*/
|
|
121
|
+
declare function calculateSimpleTabWidth(currentXPx: number): number;
|
|
122
|
+
//#endregion
|
|
123
|
+
export { DEFAULT_TAB_INTERVAL_TWIPS, PIXELS_PER_INCH, TWIPS_PER_INCH, TabAlignment, TabContext, TabFollowingContent, TabLeader, TabStop, TabWidthResult, calculateSimpleTabWidth, calculateTabWidth, computeTabStops, pixelsToTwips, twipsToPixels };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/tabCalculator.ts
|
|
2
|
+
/** Default tab interval: 720 twips = 0.5 inch */
|
|
3
|
+
const DEFAULT_TAB_INTERVAL_TWIPS = 720;
|
|
4
|
+
/** Twips per inch */
|
|
5
|
+
const TWIPS_PER_INCH = 1440;
|
|
6
|
+
/** Pixels per inch (96 dpi standard for CSS) */
|
|
7
|
+
const PIXELS_PER_INCH = 96;
|
|
8
|
+
/** Ignore sub-twip round trips when the cursor already occupies a tab stop. */
|
|
9
|
+
const TAB_POSITION_EPSILON_TWIPS = .01;
|
|
10
|
+
/** Positions closer than this are treated as the same tab stop. */
|
|
11
|
+
const TAB_STOP_MATCH_TOLERANCE_TWIPS = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Convert twips to pixels
|
|
14
|
+
* @param twips - Value in twips (1/1440 inch)
|
|
15
|
+
* @returns Value in pixels (at 96 dpi)
|
|
16
|
+
*/
|
|
17
|
+
function twipsToPixels(twips) {
|
|
18
|
+
return twips / TWIPS_PER_INCH * 96;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert pixels to twips
|
|
22
|
+
* @param pixels - Value in pixels (at 96 dpi)
|
|
23
|
+
* @returns Value in twips (1/1440 inch)
|
|
24
|
+
*/
|
|
25
|
+
function pixelsToTwips(pixels) {
|
|
26
|
+
return pixels / 96 * TWIPS_PER_INCH;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compute the list of effective tab stops for a paragraph
|
|
30
|
+
*
|
|
31
|
+
* Merges explicit stops with default stops at regular intervals.
|
|
32
|
+
* Explicit stops before the left indent remain available to a hanging first
|
|
33
|
+
* line. Continuation lines naturally skip them because their cursor already
|
|
34
|
+
* starts at the left indent.
|
|
35
|
+
*
|
|
36
|
+
* @param context - Tab context with explicit stops and settings
|
|
37
|
+
* @returns Sorted array of tab stops in twips
|
|
38
|
+
*/
|
|
39
|
+
function computeTabStops(context) {
|
|
40
|
+
const { explicitStops = [], defaultTabInterval = 720, leftIndent = 0 } = context;
|
|
41
|
+
const tabInterval = Number.isFinite(defaultTabInterval) && defaultTabInterval >= TAB_STOP_MATCH_TOLERANCE_TWIPS ? defaultTabInterval : 720;
|
|
42
|
+
const validExplicitStops = explicitStops.filter((stop) => stop.val !== "clear" && stop.val !== "bar");
|
|
43
|
+
const clearPositions = explicitStops.filter((stop) => stop.val === "clear").map((stop) => stop.pos);
|
|
44
|
+
let maxExplicit = 0;
|
|
45
|
+
for (const stop of validExplicitStops) maxExplicit = Math.max(maxExplicit, stop.pos);
|
|
46
|
+
const stops = explicitStops.filter((stop) => stop.val !== "clear");
|
|
47
|
+
const hasLeftIndentClear = clearPositions.some((p) => Math.abs(p - leftIndent) < TAB_STOP_MATCH_TOLERANCE_TWIPS);
|
|
48
|
+
const addsImplicitLeftIndent = leftIndent > 0 && !hasLeftIndentClear && !validExplicitStops.some((stop) => stop.pos <= leftIndent);
|
|
49
|
+
if (addsImplicitLeftIndent) stops.push({
|
|
50
|
+
val: "start",
|
|
51
|
+
pos: leftIndent,
|
|
52
|
+
leader: "none"
|
|
53
|
+
});
|
|
54
|
+
let pos = Math.floor(Math.max(maxExplicit, leftIndent) / tabInterval) * tabInterval;
|
|
55
|
+
const limitPos = leftIndent + 14400;
|
|
56
|
+
while (pos < limitPos) {
|
|
57
|
+
pos += tabInterval;
|
|
58
|
+
let hasExplicitStop = false;
|
|
59
|
+
for (const stop of stops) if (Math.abs(stop.pos - pos) < TAB_STOP_MATCH_TOLERANCE_TWIPS) {
|
|
60
|
+
hasExplicitStop = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
let hasClearStop = false;
|
|
64
|
+
for (const clearPos of clearPositions) if (Math.abs(clearPos - pos) < TAB_STOP_MATCH_TOLERANCE_TWIPS) {
|
|
65
|
+
hasClearStop = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
const isAtLeftIndent = addsImplicitLeftIndent && Math.abs(pos - leftIndent) < TAB_STOP_MATCH_TOLERANCE_TWIPS;
|
|
69
|
+
if (!hasExplicitStop && !hasClearStop && !isAtLeftIndent) stops.push({
|
|
70
|
+
val: "start",
|
|
71
|
+
pos,
|
|
72
|
+
leader: "none"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return stops.toSorted((a, b) => a.pos - b.pos);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Calculate the width of a tab character.
|
|
79
|
+
*
|
|
80
|
+
* Finds the next tab stop after the current position and computes the width
|
|
81
|
+
* needed to reach it. For `end`/`center`/`decimal` stops the content after the
|
|
82
|
+
* tab is anchored against the stop, so the caller supplies its measured width
|
|
83
|
+
* via {@link TabFollowingContent}.
|
|
84
|
+
*
|
|
85
|
+
* @param currentXPx - Current horizontal position in pixels (from the content
|
|
86
|
+
* area's left edge — the same origin tab stop positions are measured from)
|
|
87
|
+
* @param context - Tab context with stops and settings
|
|
88
|
+
* @param following - Measured widths of the content after the tab
|
|
89
|
+
* @returns Tab width result with width in pixels
|
|
90
|
+
*/
|
|
91
|
+
function calculateTabWidth(currentXPx, context, following = {}) {
|
|
92
|
+
const { defaultTabInterval = 720 } = context;
|
|
93
|
+
const currentXTwips = pixelsToTwips(currentXPx);
|
|
94
|
+
const nextStop = computeTabStops(context).find((stop) => stop.pos > currentXTwips + TAB_POSITION_EPSILON_TWIPS);
|
|
95
|
+
if (!nextStop) {
|
|
96
|
+
const defaultTabPx = twipsToPixels(defaultTabInterval);
|
|
97
|
+
let tabWidth = defaultTabPx - currentXPx % defaultTabPx;
|
|
98
|
+
if (tabWidth <= 0) tabWidth = defaultTabPx;
|
|
99
|
+
return {
|
|
100
|
+
width: tabWidth,
|
|
101
|
+
alignment: "default"
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let width = twipsToPixels(nextStop.pos) - currentXPx;
|
|
105
|
+
if (nextStop.val === "center") width -= (following.followingWidth ?? 0) / 2;
|
|
106
|
+
else if (nextStop.val === "end") width -= following.followingWidth ?? 0;
|
|
107
|
+
else if (nextStop.val === "decimal") width -= following.decimalPrefixWidth ?? 0;
|
|
108
|
+
else if (nextStop.val === "bar") return {
|
|
109
|
+
width: 0,
|
|
110
|
+
...nextStop.leader !== void 0 ? { leader: nextStop.leader } : {},
|
|
111
|
+
alignment: "bar"
|
|
112
|
+
};
|
|
113
|
+
if (width <= 0) {
|
|
114
|
+
const defaultTabPx = twipsToPixels(defaultTabInterval);
|
|
115
|
+
let fallbackWidth = defaultTabPx - currentXPx % defaultTabPx;
|
|
116
|
+
if (fallbackWidth <= 0) fallbackWidth = defaultTabPx;
|
|
117
|
+
return {
|
|
118
|
+
width: fallbackWidth,
|
|
119
|
+
alignment: "default"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
width,
|
|
124
|
+
...nextStop.leader !== void 0 ? { leader: nextStop.leader } : {},
|
|
125
|
+
alignment: nextStop.val
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Calculate tab width with simple default stops
|
|
130
|
+
*
|
|
131
|
+
* Simplified version for basic tab rendering without explicit stops.
|
|
132
|
+
* Uses the default 0.5 inch (48px) tab interval.
|
|
133
|
+
*
|
|
134
|
+
* @param currentXPx - Current horizontal position in pixels
|
|
135
|
+
* @returns Width of the tab in pixels
|
|
136
|
+
*/
|
|
137
|
+
function calculateSimpleTabWidth(currentXPx) {
|
|
138
|
+
const defaultTabPx = twipsToPixels(720);
|
|
139
|
+
let tabWidth = defaultTabPx - currentXPx % defaultTabPx;
|
|
140
|
+
if (tabWidth < defaultTabPx / 4) tabWidth += defaultTabPx;
|
|
141
|
+
return tabWidth;
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
export { DEFAULT_TAB_INTERVAL_TWIPS, PIXELS_PER_INCH, TWIPS_PER_INCH, calculateSimpleTabWidth, calculateTabWidth, computeTabStops, pixelsToTwips, twipsToPixels };
|
|
@@ -6,10 +6,30 @@ type TableCellBlockPlacement = {
|
|
|
6
6
|
contentHeight: number;
|
|
7
7
|
leadingSpacing: number;
|
|
8
8
|
};
|
|
9
|
+
declare const TABLE_CELL_FLOW_POSITION: {
|
|
10
|
+
readonly start: "start";
|
|
11
|
+
readonly interior: "interior";
|
|
12
|
+
};
|
|
13
|
+
type TableCellFlowPosition = (typeof TABLE_CELL_FLOW_POSITION)[keyof typeof TABLE_CELL_FLOW_POSITION];
|
|
14
|
+
declare const TABLE_CELL_TRAILING_SPACING_TYPE: {
|
|
15
|
+
readonly none: "none";
|
|
16
|
+
readonly authored: "authored";
|
|
17
|
+
readonly automatic: "automatic";
|
|
18
|
+
};
|
|
19
|
+
type TableCellTrailingSpacing = {
|
|
20
|
+
type: typeof TABLE_CELL_TRAILING_SPACING_TYPE.none;
|
|
21
|
+
} | {
|
|
22
|
+
type: typeof TABLE_CELL_TRAILING_SPACING_TYPE.authored;
|
|
23
|
+
value: number;
|
|
24
|
+
} | {
|
|
25
|
+
type: typeof TABLE_CELL_TRAILING_SPACING_TYPE.automatic;
|
|
26
|
+
value: number;
|
|
27
|
+
};
|
|
9
28
|
type TableCellFlowState = {
|
|
10
29
|
height: number;
|
|
30
|
+
position: TableCellFlowPosition;
|
|
11
31
|
previousParagraphWasEmpty: boolean;
|
|
12
|
-
trailingSpacing:
|
|
32
|
+
trailingSpacing: TableCellTrailingSpacing;
|
|
13
33
|
};
|
|
14
34
|
declare const createTableCellFlowState: () => TableCellFlowState;
|
|
15
35
|
declare const finishTableCellFlow: (state: TableCellFlowState) => number;
|