@stll/folio-core 0.48.0 → 0.50.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/headless.d.ts +37 -1
- package/dist/ai-edits/headless.js +47 -16
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/layoutPipeline.js +11 -1
- package/dist/display-list/build/paragraphPrimitives.js +63 -7
- 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.d.ts +10 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
- package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
- package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
- package/dist/display-list/selectDisplayPages.d.ts +6 -0
- package/dist/display-list/selectDisplayPages.js +44 -0
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- 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/groupDrawingParser.d.ts +7 -1
- package/dist/docx/groupDrawingParser.js +123 -32
- 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.js +91 -7
- package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
- package/dist/docx/runParser.js +10 -0
- 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 +5 -4
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/textBoxParser.d.ts +10 -2
- package/dist/docx/textBoxParser.js +9 -3
- package/dist/export-pdf.d.ts +35 -2
- package/dist/export-pdf.js +51 -20
- package/dist/fonts/fontsourceFaces.d.ts +47 -0
- package/dist/fonts/fontsourceFaces.js +230 -0
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
- package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
- package/dist/layout-bridge/convert/flowBorders.js +31 -0
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
- package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
- package/dist/layout-bridge/convert/imageConversion.js +118 -0
- package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
- package/dist/layout-bridge/convert/listMarkers.js +98 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
- package/dist/layout-bridge/convert/noteReferences.js +29 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
- package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
- package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
- package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
- package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
- package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
- package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
- package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
- package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
- package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
- package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
- package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/tableConversion.js +303 -0
- package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
- package/dist/layout-bridge/convert/textBoxFill.js +42 -0
- package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
- package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
- package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
- package/dist/layout-bridge/engine/hitTest.js +1 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +1 -1
- package/dist/layout-engine/imageLayout.d.ts +9 -0
- package/dist/layout-engine/imageLayout.js +48 -0
- package/dist/layout-engine/index.d.ts +4 -19
- package/dist/layout-engine/index.js +14 -910
- package/dist/layout-engine/keep-together.d.ts +5 -2
- package/dist/layout-engine/keep-together.js +5 -1
- package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
- package/dist/layout-engine/layoutFlowShared.js +8 -0
- package/dist/layout-engine/measure/advanceComposition.js +49 -3
- package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
- package/dist/layout-engine/measure/crossRunWords.js +172 -0
- package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
- package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
- package/dist/layout-engine/measure/justification.d.ts +32 -0
- package/dist/layout-engine/measure/justification.js +83 -0
- package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
- package/dist/layout-engine/measure/lineFitting.js +145 -0
- package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
- package/dist/layout-engine/measure/lineTypography.js +124 -0
- package/dist/layout-engine/measure/measureBlocks.js +7 -4
- package/dist/layout-engine/measure/measureContainer.js +46 -3
- package/dist/layout-engine/measure/measureHelpers.js +13 -3
- package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
- package/dist/layout-engine/measure/measureParagraph.js +41 -780
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
- package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
- package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
- package/dist/layout-engine/measure/paragraphTabs.js +78 -0
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/paginator.d.ts +2 -0
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphLayout.d.ts +23 -0
- package/dist/layout-engine/paragraphLayout.js +204 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/sectionLayout.d.ts +45 -0
- package/dist/layout-engine/sectionLayout.js +216 -0
- package/dist/layout-engine/tableLayout.d.ts +17 -0
- package/dist/layout-engine/tableLayout.js +369 -0
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +22 -1
- package/dist/layout-engine/textBoxLayout.d.ts +15 -0
- package/dist/layout-engine/textBoxLayout.js +137 -0
- package/dist/layout-engine/types.d.ts +21 -2
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +41 -4
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
- package/dist/paged-layout/sectionBlockWidths.js +1 -1
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.js +90 -0
- 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 +46 -22
- package/dist/prosemirror/conversion/toProseDoc.js +42 -19
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- 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 +3 -0
- package/dist/prosemirror/listMarker.js +2 -2
- 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 +37 -5
- package/dist/server.d.ts +3 -2
- package/dist/types/content.d.ts +2 -2
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- package/dist/layout-engine/justifiedLineFit.js +0 -6
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
2
|
+
import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
|
|
3
|
+
import { findGraphemeBreaks, isHangingPunctuation } from "./lineBreaks.js";
|
|
4
|
+
import { twipsToPx } from "./measureHelpers.js";
|
|
5
|
+
import { measureTextWidth } from "./measureProvider.js";
|
|
6
|
+
import { WIDTH_TOLERANCE, isTextRun, runToFontStyle } from "./paragraphMeasureShared.js";
|
|
7
|
+
//#region src/layout-engine/measure/lineFitting.ts
|
|
8
|
+
/**
|
|
9
|
+
* Line-fitting helpers: character-level fit search, the hyphenation zone,
|
|
10
|
+
* final-candidate admission, and the trailing whitespace, letter-spacing and
|
|
11
|
+
* hanging-punctuation widths a line break may exclude.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
15
|
+
* Returns the number of characters that fit (at least 1 if `forceMin` is true).
|
|
16
|
+
*/
|
|
17
|
+
function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
18
|
+
const boundaries = findGraphemeBreaks(text, policy);
|
|
19
|
+
let lo = 0;
|
|
20
|
+
let hi = boundaries.length - 1;
|
|
21
|
+
let best = 0;
|
|
22
|
+
while (lo <= hi) {
|
|
23
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
24
|
+
const boundary = boundaries[mid];
|
|
25
|
+
if (measureTextWidth(text.slice(0, boundary), style) <= maxWidth) {
|
|
26
|
+
best = boundary;
|
|
27
|
+
lo = mid + 1;
|
|
28
|
+
} else hi = mid - 1;
|
|
29
|
+
}
|
|
30
|
+
return forceMin && best === 0 ? boundaries.at(0) ?? 0 : best;
|
|
31
|
+
}
|
|
32
|
+
function exceedsHyphenationZone(line, zoneTwips) {
|
|
33
|
+
if (line.width <= 0) return true;
|
|
34
|
+
const visibleWidth = Math.max(0, line.width - line.trailingWhitespaceWidth);
|
|
35
|
+
return Math.max(0, line.availableWidth - visibleWidth) > twipsToPx(zoneTwips) + WIDTH_TOLERANCE;
|
|
36
|
+
}
|
|
37
|
+
const isIgnorableFinalTailRun = (run) => run.kind === "renderedPageBreak" || isTextRun(run) && run.text.length === 0;
|
|
38
|
+
function isFinalTextCandidate(block, runIndex, nextBreak) {
|
|
39
|
+
const currentRun = block.runs[runIndex];
|
|
40
|
+
if (!currentRun || !isTextRun(currentRun) || nextBreak !== currentRun.text.length) return false;
|
|
41
|
+
for (let index = runIndex + 1; index < block.runs.length; index += 1) {
|
|
42
|
+
const run = block.runs[index];
|
|
43
|
+
if (run && isIgnorableFinalTailRun(run)) continue;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fit of a text candidate on a justified line: the paragraph's final word may
|
|
50
|
+
* be admitted by shrinking spaces, which then needs a paint plan.
|
|
51
|
+
*/
|
|
52
|
+
function resolveTextCandidateFit(isFinalCandidate, candidateWidth, availableWidth, tolerancePx) {
|
|
53
|
+
if (!isFinalCandidate) return {
|
|
54
|
+
type: "ordinary",
|
|
55
|
+
tolerancePx
|
|
56
|
+
};
|
|
57
|
+
return admitFinalCandidate(candidateWidth, availableWidth, tolerancePx);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A paragraph's last line is not stretched, so a final word admitted by space
|
|
61
|
+
* contraction needs an explicit paint plan; otherwise it would paint past the
|
|
62
|
+
* measure. Sub-pixel rounding overflow stays on the ordinary path.
|
|
63
|
+
*/
|
|
64
|
+
function admitFinalCandidate(candidateWidth, availableWidth, tolerancePx) {
|
|
65
|
+
const contractionPx = candidateWidth - availableWidth;
|
|
66
|
+
if (contractionPx <= .5 || contractionPx > tolerancePx) return {
|
|
67
|
+
type: "ordinary",
|
|
68
|
+
tolerancePx
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
type: "final-contraction-admitted",
|
|
72
|
+
tolerancePx,
|
|
73
|
+
paint: {
|
|
74
|
+
type: "space-contraction",
|
|
75
|
+
contractionPx
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function trimTrailingSpacesAndTabs(text) {
|
|
80
|
+
let end = text.length;
|
|
81
|
+
while (end > 0) {
|
|
82
|
+
const char = text[end - 1];
|
|
83
|
+
if (char !== " " && char !== " ") break;
|
|
84
|
+
end--;
|
|
85
|
+
}
|
|
86
|
+
return text.slice(0, end);
|
|
87
|
+
}
|
|
88
|
+
function measureWordWithTrailingWhitespace(word, style) {
|
|
89
|
+
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
90
|
+
const wordWidth = measureTextWidth(measuredWord, style);
|
|
91
|
+
const trailingWhitespace = word.slice(measuredWord.length);
|
|
92
|
+
if (trailingWhitespace.length === 0) return {
|
|
93
|
+
measuredWord,
|
|
94
|
+
wordWidth,
|
|
95
|
+
fullWordWidth: wordWidth
|
|
96
|
+
};
|
|
97
|
+
if (style.kerning || trailingWhitespace.includes(" ")) return {
|
|
98
|
+
measuredWord,
|
|
99
|
+
wordWidth,
|
|
100
|
+
fullWordWidth: measureTextWidth(word, style)
|
|
101
|
+
};
|
|
102
|
+
const trailingWhitespaceWidth = measureTextWidth(trailingWhitespace, style);
|
|
103
|
+
const boundarySpacing = measuredWord.length > 0 ? (style.letterSpacing ?? 0) * getHorizontalScaleFactor(style.horizontalScale) : 0;
|
|
104
|
+
return {
|
|
105
|
+
measuredWord,
|
|
106
|
+
wordWidth,
|
|
107
|
+
fullWordWidth: wordWidth + trailingWhitespaceWidth + boundarySpacing
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const scaledLetterSpacing = (style) => (style.letterSpacing ?? 0) * getHorizontalScaleFactor(style.horizontalScale);
|
|
111
|
+
/**
|
|
112
|
+
* Character spacing still owed after the last character placed on the line,
|
|
113
|
+
* or 0 when the line has no preceding character at `charIndex` of `runIndex`.
|
|
114
|
+
* Within a run it is the run's own spacing; at a boundary between two text
|
|
115
|
+
* runs it is the preceding run's spacing on its final character.
|
|
116
|
+
*/
|
|
117
|
+
function precedingLetterSpacing({ block, line, runIndex, charIndex, runStyle }) {
|
|
118
|
+
if (line.fromRun === line.toRun && line.fromChar === line.toChar) return 0;
|
|
119
|
+
if (line.toRun === runIndex && line.toChar === charIndex) return scaledLetterSpacing(runStyle);
|
|
120
|
+
if (charIndex !== 0 || line.toRun !== runIndex - 1) return 0;
|
|
121
|
+
const previousRun = block.runs[runIndex - 1];
|
|
122
|
+
if (!previousRun || !isTextRun(previousRun) || previousRun.text.length === 0 || line.toChar !== previousRun.text.length) return 0;
|
|
123
|
+
return scaledLetterSpacing(runToFontStyle(previousRun));
|
|
124
|
+
}
|
|
125
|
+
function trailingCodePoint(text) {
|
|
126
|
+
if (text.length === 0) return;
|
|
127
|
+
const trailing = text.charCodeAt(text.length - 1);
|
|
128
|
+
const preceding = text.charCodeAt(text.length - 2);
|
|
129
|
+
const start = trailing >= 56320 && trailing <= 57343 && preceding >= 55296 && preceding <= 56319 ? text.length - 2 : text.length - 1;
|
|
130
|
+
return text.slice(start);
|
|
131
|
+
}
|
|
132
|
+
const usesDefaultHangingPunctuationClassifier = () => {
|
|
133
|
+
return (getLineBreakProvider().isHangingPunctuation ?? defaultLineBreakProvider.isHangingPunctuation) === defaultLineBreakProvider.isHangingPunctuation;
|
|
134
|
+
};
|
|
135
|
+
function trailingHangingPunctuationWidth(text, style, policy, enabled) {
|
|
136
|
+
if (!enabled || text.length === 0) return 0;
|
|
137
|
+
const trailing = trailingCodePoint(text);
|
|
138
|
+
if (usesDefaultHangingPunctuationClassifier() && !isHangingPunctuation(trailing ?? "", policy)) return 0;
|
|
139
|
+
const lastStart = findGraphemeBreaks(text, policy).at(-2) ?? 0;
|
|
140
|
+
const lastGrapheme = text.slice(lastStart);
|
|
141
|
+
if (!isHangingPunctuation(lastGrapheme, policy)) return 0;
|
|
142
|
+
return measureTextWidth(lastGrapheme, style);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { exceedsHyphenationZone, findMaxFittingLength, isFinalTextCandidate, isIgnorableFinalTailRun, measureWordWithTrailingWhitespace, precedingLetterSpacing, resolveTextCandidateFit, trailingHangingPunctuationWidth, trimTrailingSpacesAndTabs };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ParagraphAttrs, ParagraphSpacing, TextRun } from "../types.js";
|
|
2
|
+
import { FontMetrics, FontStyle } from "./measureTypes.js";
|
|
3
|
+
import { LineTypography } from "./paragraphMeasureShared.js";
|
|
4
|
+
//#region src/layout-engine/measure/lineTypography.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Line-HEIGHT style for a text run. Word derives a CJK line's height from an
|
|
7
|
+
* East-Asian face, not the run's ascii font: real documents routinely put CJK
|
|
8
|
+
* glyphs in runs whose ascii/eastAsia fonts are Latin (e.g. `w:eastAsia`
|
|
9
|
+
* "Century"), and Word font-links those glyphs to the default East-Asian face
|
|
10
|
+
* and uses its taller single-line height (≈1.303 vs the ≈1.15 Latin default).
|
|
11
|
+
*
|
|
12
|
+
* When the run holds CJK text, swap `fontFamily` to the face whose
|
|
13
|
+
* `singleLineRatio` Word would actually use: the run's `w:eastAsia` font when
|
|
14
|
+
* it is a real CJK face, the run's own ascii font when THAT is a CJK face, or
|
|
15
|
+
* `CJK_FALLBACK_FONT_FAMILY` otherwise. Non-CJK runs return `baseStyle`
|
|
16
|
+
* unchanged, so Latin-only documents are unaffected by construction.
|
|
17
|
+
*
|
|
18
|
+
* The result feeds `updateMaxFont` (line height) ONLY — width measurement
|
|
19
|
+
* keeps the base style so wrapping is untouched.
|
|
20
|
+
*/
|
|
21
|
+
declare function cjkLineHeightStyle(run: TextRun, baseStyle: FontStyle): FontStyle;
|
|
22
|
+
/** Pick the dominant typography metrics for a run containing complex script. */
|
|
23
|
+
declare function complexScriptLineHeightStyle(run: TextRun, baseStyle: FontStyle): FontStyle;
|
|
24
|
+
/**
|
|
25
|
+
* Calculate typography metrics from font size and spacing settings
|
|
26
|
+
*
|
|
27
|
+
* @param fontSize - Font size in points
|
|
28
|
+
* @param spacing - Paragraph spacing settings
|
|
29
|
+
* @param metrics - Pre-calculated font metrics (in pixels)
|
|
30
|
+
*/
|
|
31
|
+
declare function calculateTypographyMetrics(fontSize: number, spacing?: ParagraphSpacing, metrics?: FontMetrics | null): LineTypography;
|
|
32
|
+
/**
|
|
33
|
+
* Calculate metrics for an empty paragraph.
|
|
34
|
+
*
|
|
35
|
+
* Word renders an empty paragraph as a single readable line — its line
|
|
36
|
+
* height never collapses below 1.15 × font size, even when the doc
|
|
37
|
+
* explicitly writes `<w:line w:val="240"/>` (1.0×). The floor is scoped to
|
|
38
|
+
* `auto`/`atLeast` line rules; `exact` means exact (per OOXML §17.3.1.33)
|
|
39
|
+
* and stays untouched.
|
|
40
|
+
*/
|
|
41
|
+
declare function calculateEmptyParagraphMetrics(fontSize: number, spacing?: ParagraphSpacing, fontFamily?: string, attrs?: ParagraphAttrs): LineTypography;
|
|
42
|
+
declare function applyDocumentGrid(typography: LineTypography, attrs: ParagraphAttrs | undefined, spacing: ParagraphSpacing | undefined): LineTypography;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { applyDocumentGrid, calculateEmptyParagraphMetrics, calculateTypographyMetrics, cjkLineHeightStyle, complexScriptLineHeightStyle };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
|
+
import { hasCjk, hasComplexScript, hasEastAsiaSlotText } from "../../utils/scriptSegments.js";
|
|
3
|
+
import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
|
|
4
|
+
import { ptToPx } from "./measureHelpers.js";
|
|
5
|
+
import { getFontMetrics } from "./measureProvider.js";
|
|
6
|
+
import "./paragraphMeasureShared.js";
|
|
7
|
+
//#region src/layout-engine/measure/lineTypography.ts
|
|
8
|
+
/**
|
|
9
|
+
* Line height and typography metrics: font-driven line heights, East Asian
|
|
10
|
+
* and complex-script line height styles, empty-paragraph metrics, and
|
|
11
|
+
* document-grid line pitch.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Line-HEIGHT style for a text run. Word derives a CJK line's height from an
|
|
15
|
+
* East-Asian face, not the run's ascii font: real documents routinely put CJK
|
|
16
|
+
* glyphs in runs whose ascii/eastAsia fonts are Latin (e.g. `w:eastAsia`
|
|
17
|
+
* "Century"), and Word font-links those glyphs to the default East-Asian face
|
|
18
|
+
* and uses its taller single-line height (≈1.303 vs the ≈1.15 Latin default).
|
|
19
|
+
*
|
|
20
|
+
* When the run holds CJK text, swap `fontFamily` to the face whose
|
|
21
|
+
* `singleLineRatio` Word would actually use: the run's `w:eastAsia` font when
|
|
22
|
+
* it is a real CJK face, the run's own ascii font when THAT is a CJK face, or
|
|
23
|
+
* `CJK_FALLBACK_FONT_FAMILY` otherwise. Non-CJK runs return `baseStyle`
|
|
24
|
+
* unchanged, so Latin-only documents are unaffected by construction.
|
|
25
|
+
*
|
|
26
|
+
* The result feeds `updateMaxFont` (line height) ONLY — width measurement
|
|
27
|
+
* keeps the base style so wrapping is untouched.
|
|
28
|
+
*/
|
|
29
|
+
function cjkLineHeightStyle(run, baseStyle) {
|
|
30
|
+
if (!run.text) return baseStyle;
|
|
31
|
+
const eastAsia = baseStyle.eastAsiaFontFamily;
|
|
32
|
+
const eastAsiaFace = eastAsia !== void 0 && isCjkFont(eastAsia);
|
|
33
|
+
if (!hasCjk(run.text) && !(eastAsiaFace && hasEastAsiaSlotText(run.text, baseStyle.eastAsiaHint))) return baseStyle;
|
|
34
|
+
if (eastAsia !== void 0 && eastAsiaFace) {
|
|
35
|
+
const result = {
|
|
36
|
+
...baseStyle,
|
|
37
|
+
fontFamily: eastAsia
|
|
38
|
+
};
|
|
39
|
+
delete result.alternateFontFamily;
|
|
40
|
+
if (baseStyle.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = baseStyle.eastAsiaAlternateFontFamily;
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
if (isCjkFont(baseStyle.fontFamily ?? "Times New Roman")) return baseStyle;
|
|
44
|
+
return {
|
|
45
|
+
...baseStyle,
|
|
46
|
+
fontFamily: CJK_FALLBACK_FONT_FAMILY
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Pick the dominant typography metrics for a run containing complex script. */
|
|
50
|
+
function complexScriptLineHeightStyle(run, baseStyle) {
|
|
51
|
+
if (!hasComplexScriptFormatting(run) || !run.forceComplexScript && !hasComplexScript(run.text)) return baseStyle;
|
|
52
|
+
const complexStyle = applyComplexScriptFormatting(baseStyle, run);
|
|
53
|
+
return (complexStyle.fontSize ?? 11) >= (baseStyle.fontSize ?? 11) ? complexStyle : baseStyle;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Calculate typography metrics from font size and spacing settings
|
|
57
|
+
*
|
|
58
|
+
* @param fontSize - Font size in points
|
|
59
|
+
* @param spacing - Paragraph spacing settings
|
|
60
|
+
* @param metrics - Pre-calculated font metrics (in pixels)
|
|
61
|
+
*/
|
|
62
|
+
function calculateTypographyMetrics(fontSize, spacing, metrics) {
|
|
63
|
+
const fontSizePx = ptToPx(fontSize);
|
|
64
|
+
const ascent = metrics?.ascent ?? fontSizePx * .8;
|
|
65
|
+
const descent = metrics?.descent ?? fontSizePx * .2;
|
|
66
|
+
const singleLineBase = fontSizePx * (metrics?.singleLineRatio ?? 1.15);
|
|
67
|
+
let lineHeight;
|
|
68
|
+
if (spacing?.lineRule === "exact" && spacing.line !== void 0) lineHeight = spacing.line;
|
|
69
|
+
else if (spacing?.lineRule === "atLeast" && spacing.line !== void 0) {
|
|
70
|
+
const defaultHeight = singleLineBase * 1;
|
|
71
|
+
lineHeight = Math.max(spacing.line, defaultHeight);
|
|
72
|
+
} else if (spacing?.line !== void 0 && spacing.lineUnit === "multiplier") lineHeight = singleLineBase * spacing.line;
|
|
73
|
+
else if (spacing?.line !== void 0 && spacing.lineUnit === "px") lineHeight = spacing.line;
|
|
74
|
+
else lineHeight = singleLineBase * 1;
|
|
75
|
+
return {
|
|
76
|
+
ascent,
|
|
77
|
+
descent,
|
|
78
|
+
lineHeight
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Word's "single line spacing" floor (≈ 1.15×) applied to empty paragraphs
|
|
83
|
+
* with `auto`/`atLeast` line rules. Without this, narrow-metric fonts
|
|
84
|
+
* (Arial Narrow, OS/2 ratio ≈ 1.117) collapse empty rows visibly tighter
|
|
85
|
+
* than Word renders them. See eigenpal #391/#394.
|
|
86
|
+
*/
|
|
87
|
+
const WORD_SINGLE_LINE_FLOOR = 1.15;
|
|
88
|
+
/**
|
|
89
|
+
* Calculate metrics for an empty paragraph.
|
|
90
|
+
*
|
|
91
|
+
* Word renders an empty paragraph as a single readable line — its line
|
|
92
|
+
* height never collapses below 1.15 × font size, even when the doc
|
|
93
|
+
* explicitly writes `<w:line w:val="240"/>` (1.0×). The floor is scoped to
|
|
94
|
+
* `auto`/`atLeast` line rules; `exact` means exact (per OOXML §17.3.1.33)
|
|
95
|
+
* and stays untouched.
|
|
96
|
+
*/
|
|
97
|
+
function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
|
|
98
|
+
const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
|
|
99
|
+
fontSize,
|
|
100
|
+
fontFamily: fontFamily ?? "Times New Roman",
|
|
101
|
+
...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
|
|
102
|
+
}));
|
|
103
|
+
const lineRule = spacing?.lineRule ?? "auto";
|
|
104
|
+
if (lineRule === "auto" || lineRule === "atLeast") {
|
|
105
|
+
const floor = ptToPx(fontSize) * WORD_SINGLE_LINE_FLOOR;
|
|
106
|
+
if (result.lineHeight < floor) return applyDocumentGrid({
|
|
107
|
+
...result,
|
|
108
|
+
lineHeight: floor
|
|
109
|
+
}, attrs, spacing);
|
|
110
|
+
}
|
|
111
|
+
return applyDocumentGrid(result, attrs, spacing);
|
|
112
|
+
}
|
|
113
|
+
function applyDocumentGrid(typography, attrs, spacing) {
|
|
114
|
+
const pitch = attrs?.documentGridLinePitch;
|
|
115
|
+
if (pitch === void 0 || pitch <= 0 || attrs?.snapToGrid === false) return typography;
|
|
116
|
+
if (spacing?.lineRule === "exact") return typography;
|
|
117
|
+
const lineHeight = Math.ceil(typography.lineHeight / pitch - 1e-9) * pitch;
|
|
118
|
+
return lineHeight === typography.lineHeight ? typography : {
|
|
119
|
+
...typography,
|
|
120
|
+
lineHeight
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { applyDocumentGrid, calculateEmptyParagraphMetrics, calculateTypographyMetrics, cjkLineHeightStyle, complexScriptLineHeightStyle };
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { hasPageBreakBefore } from "../keep-together.js";
|
|
2
2
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
3
3
|
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
4
|
+
import { resolveTableSpacing } from "../paragraphSpacing.js";
|
|
4
5
|
import { sectionStartAdvanceOf } from "../section-breaks.js";
|
|
5
6
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
6
7
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
7
8
|
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "../types.js";
|
|
8
9
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
10
|
+
import { findClearLineY } from "./floatingLineClearance.js";
|
|
9
11
|
import { resolveFloatingTablePageX } from "./floatingTablePosition.js";
|
|
10
12
|
import { getHyphenationDictionaryMisses } from "./hyphenationDictionaries.js";
|
|
11
|
-
import {
|
|
13
|
+
import { measureParagraph } from "./measureParagraph.js";
|
|
12
14
|
import { buildTableCellFloatingZones, getTableCellFloatingImages } from "./tableCellFloating.js";
|
|
13
15
|
import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
|
|
14
16
|
import { buildTableCellGrid, getFirstAvailableColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
|
|
@@ -97,6 +99,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
|
|
102
|
+
const spacingRows = resolveTableSpacing(tableBlock).rows;
|
|
100
103
|
const cellSpacing = tableBlock.cellSpacing;
|
|
101
104
|
const bordersSeparated = (cellSpacing ?? 0) > 0;
|
|
102
105
|
const rowCount = tableBlock.rows.length;
|
|
@@ -104,7 +107,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
104
107
|
const rows = tableBlock.rows.map((row, rowIdx) => {
|
|
105
108
|
let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
|
|
106
109
|
return {
|
|
107
|
-
cells: row.cells.map((cell) => {
|
|
110
|
+
cells: row.cells.map((cell, cellIdx) => {
|
|
108
111
|
const colSpan = cell.colSpan ?? 1;
|
|
109
112
|
let cellWidth = 0;
|
|
110
113
|
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
@@ -121,7 +124,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
121
124
|
width: cellWidth,
|
|
122
125
|
height: 0
|
|
123
126
|
};
|
|
124
|
-
if (!keepSingleLine) wrapTableCellParagraphsAroundFloats(cell, cellMeasure, cellContentWidth, fieldValues);
|
|
127
|
+
if (!keepSingleLine) wrapTableCellParagraphsAroundFloats(spacingRows[rowIdx]?.cells[cellIdx] ?? cell, cellMeasure, cellContentWidth, fieldValues);
|
|
125
128
|
if (cell.colSpan !== void 0) cellMeasure.colSpan = cell.colSpan;
|
|
126
129
|
if (cell.rowSpan !== void 0) cellMeasure.rowSpan = cell.rowSpan;
|
|
127
130
|
return cellMeasure;
|
|
@@ -141,7 +144,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
141
144
|
}));
|
|
142
145
|
continue;
|
|
143
146
|
}
|
|
144
|
-
const sourceRowCells =
|
|
147
|
+
const sourceRowCells = spacingRows[rowIdx]?.cells;
|
|
145
148
|
let maxCellHeightWithBorders = 0;
|
|
146
149
|
let maxCellInsets = 0;
|
|
147
150
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
@@ -6,6 +6,7 @@ import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
|
|
|
6
6
|
import { setMeasureProvider } from "./measureProvider.js";
|
|
7
7
|
import { canPrefetchMeasurement, prefetchMeasurement } from "./measureWorker.js";
|
|
8
8
|
import { WORKER_FONT_FINGERPRINT_TEXT, countCodePoints } from "./measureWorkerProtocol.js";
|
|
9
|
+
import { SMALL_CAPS_SCALE, smallCapsMask, smallCapsSegments } from "./smallCapsCasing.js";
|
|
9
10
|
import { getFontKerningMode } from "./textMeasurementPolicy.js";
|
|
10
11
|
import { panic } from "better-result";
|
|
11
12
|
//#region src/layout-engine/measure/measureContainer.ts
|
|
@@ -136,6 +137,7 @@ function canvasMeasureTextWidth(text, sourceStyle) {
|
|
|
136
137
|
if (!text) return 0;
|
|
137
138
|
const style = sourceStyle.forceComplexScript ? applyComplexScriptFormatting(sourceStyle, sourceStyle) : sourceStyle;
|
|
138
139
|
const measuredText = applyTextTransform(text, style);
|
|
140
|
+
if (!sourceStyle.forceComplexScript && style.fontVariant === "small-caps") return measureSmallCapsWidth(measuredText, style);
|
|
139
141
|
if (!sourceStyle.forceComplexScript && !style.letterSpacing && needsPerScriptFonts(style, measuredText)) return measureMixedScriptWidth(measuredText, style);
|
|
140
142
|
const ctx = getCanvasContext();
|
|
141
143
|
const font = buildFontString(style);
|
|
@@ -228,6 +230,38 @@ function measureMixedScriptWidth(measuredText, style) {
|
|
|
228
230
|
return width * horizontalScale;
|
|
229
231
|
}
|
|
230
232
|
/**
|
|
233
|
+
* Width of a `w:smallCaps` run: its lowercase letters (and the whitespace
|
|
234
|
+
* that follows them — see smallCapsCasing.ts) draw at `SMALL_CAPS_SCALE` of
|
|
235
|
+
* the run's size, everything else at full size. A small segment is measured
|
|
236
|
+
* at that literal, smaller font string — not the full-size advance scaled by
|
|
237
|
+
* arithmetic — so hinting and sub-pixel snapping agree with the painted DOM
|
|
238
|
+
* span, which sets this same font-size. Each segment still recurses into
|
|
239
|
+
* {@link canvasMeasureTextWidth} (so one that itself needs the East-Asian or
|
|
240
|
+
* complex-script font still gets it); letter spacing and horizontal scale
|
|
241
|
+
* apply once over the whole string, as {@link measureMixedScriptWidth} does
|
|
242
|
+
* for a script split.
|
|
243
|
+
*/
|
|
244
|
+
function measureSmallCapsWidth(measuredText, style) {
|
|
245
|
+
const letterSpacing = style.letterSpacing ?? 0;
|
|
246
|
+
const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
|
|
247
|
+
const segmentStyle = { ...style };
|
|
248
|
+
delete segmentStyle.fontVariant;
|
|
249
|
+
delete segmentStyle.letterSpacing;
|
|
250
|
+
delete segmentStyle.horizontalScale;
|
|
251
|
+
const smallSegmentStyle = {
|
|
252
|
+
...segmentStyle,
|
|
253
|
+
fontSize: (segmentStyle.fontSize ?? 11) * SMALL_CAPS_SCALE
|
|
254
|
+
};
|
|
255
|
+
let glyphWidth = 0;
|
|
256
|
+
for (const segment of smallCapsSegments(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, segment.small ? smallSegmentStyle : segmentStyle);
|
|
257
|
+
let width = glyphWidth;
|
|
258
|
+
if (letterSpacing) {
|
|
259
|
+
const codePoints = countCodePoints(measuredText);
|
|
260
|
+
if (codePoints > 1) width += letterSpacing * (codePoints - 1);
|
|
261
|
+
}
|
|
262
|
+
return width * horizontalScale;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
231
265
|
* Speculatively enqueue the slice lengths that a subsequent
|
|
232
266
|
* `findMaxFittingLength` binary search is likely to probe. We pick the
|
|
233
267
|
* geometric series (full, half, quarter, eighth) which covers the
|
|
@@ -293,18 +327,26 @@ function canvasMeasureRun(text, sourceStyle) {
|
|
|
293
327
|
...style.eastAsiaAlternateFontFamily !== void 0 ? { alternateFontFamily: style.eastAsiaAlternateFontFamily } : {}
|
|
294
328
|
}) : void 0;
|
|
295
329
|
const complexScriptFont = hasComplexScriptFormatting(style) && !style.letterSpacing ? buildFontString(scriptStyle(style, SCRIPT_CLASS.complex)) : void 0;
|
|
330
|
+
const smallCapsFont = style.fontVariant === "small-caps" ? buildFontString({
|
|
331
|
+
...style,
|
|
332
|
+
fontSize: (style.fontSize ?? 11) * SMALL_CAPS_SCALE
|
|
333
|
+
}) : void 0;
|
|
296
334
|
const letterSpacing = style.letterSpacing ?? 0;
|
|
297
335
|
const scale = getHorizontalScaleFactor(style.horizontalScale);
|
|
298
336
|
const charWidths = [];
|
|
299
337
|
let totalWidth = 0;
|
|
338
|
+
const smallCapsAt = smallCapsFont !== void 0 ? smallCapsMask(text) : void 0;
|
|
300
339
|
let offset = 0;
|
|
340
|
+
let codePointIndex = 0;
|
|
301
341
|
for (const char of text) {
|
|
302
342
|
const cp = char.codePointAt(0);
|
|
303
|
-
const
|
|
304
|
-
|
|
343
|
+
const isSmallCap = smallCapsAt?.[codePointIndex] ?? false;
|
|
344
|
+
const measured = isSmallCap ? char.toLocaleUpperCase() : applyTextTransform(char, style);
|
|
345
|
+
if (isSmallCap && smallCapsFont !== void 0) ctx.font = smallCapsFont;
|
|
346
|
+
else if (eastAsiaFont !== void 0 || complexScriptFont !== void 0) {
|
|
305
347
|
const script = scriptClassOf(cp, style.eastAsiaHint);
|
|
306
348
|
ctx.font = (script === SCRIPT_CLASS.eastAsia ? eastAsiaFont : void 0) ?? (script === SCRIPT_CLASS.complex ? complexScriptFont : void 0) ?? baseFont;
|
|
307
|
-
}
|
|
349
|
+
} else if (smallCapsFont !== void 0) ctx.font = baseFont;
|
|
308
350
|
let charWidth = ctx.measureText(measured).width;
|
|
309
351
|
if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
|
|
310
352
|
charWidth *= scale;
|
|
@@ -312,6 +354,7 @@ function canvasMeasureRun(text, sourceStyle) {
|
|
|
312
354
|
if (char.length === 2) charWidths.push(0);
|
|
313
355
|
totalWidth += charWidth;
|
|
314
356
|
offset += char.length;
|
|
357
|
+
codePointIndex += 1;
|
|
315
358
|
}
|
|
316
359
|
return {
|
|
317
360
|
width: totalWidth,
|
|
@@ -34,6 +34,18 @@ function docxScriptFontSize(fontSize) {
|
|
|
34
34
|
return Math.max(1, Math.floor(fontSize * 2 * 2 / 3)) / 2;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
+
* The `textTransform`/`fontVariant` a run's caps toggles contribute.
|
|
38
|
+
*
|
|
39
|
+
* `w:caps` wins when both are set: full-size capitals, never synthesized
|
|
40
|
+
* small ones (matching the else-if in formatToStyle.ts's live editor
|
|
41
|
+
* styling).
|
|
42
|
+
*/
|
|
43
|
+
function capsStyleOf(run) {
|
|
44
|
+
if (run.allCaps) return { textTransform: "uppercase" };
|
|
45
|
+
if (run.smallCaps) return { fontVariant: "small-caps" };
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
37
49
|
* Build a measurement `FontStyle` from a run's formatting. Single source of
|
|
38
50
|
* truth for run → FontStyle so every measurement path (layout line-breaking,
|
|
39
51
|
* click-to-position, selection rects) carries the same fields — notably
|
|
@@ -61,8 +73,7 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
|
|
|
61
73
|
...run.bold !== void 0 ? { bold: run.bold } : {},
|
|
62
74
|
...run.italic !== void 0 ? { italic: run.italic } : {},
|
|
63
75
|
...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
|
|
64
|
-
...run
|
|
65
|
-
...run.smallCaps ? { fontVariant: "small-caps" } : {},
|
|
76
|
+
...capsStyleOf(run),
|
|
66
77
|
...horizontalScale !== void 0 ? { horizontalScale } : {},
|
|
67
78
|
kerning: getRunFontKerningMode(run, fallbackFontSize) === FONT_KERNING_MODE.enabled
|
|
68
79
|
};
|
|
@@ -119,7 +130,6 @@ function clearFontResolvedCache() {
|
|
|
119
130
|
function buildFontString(style) {
|
|
120
131
|
const parts = [];
|
|
121
132
|
if (style.italic) parts.push("italic");
|
|
122
|
-
if (style.fontVariant) parts.push(style.fontVariant);
|
|
123
133
|
if (style.bold) parts.push("700");
|
|
124
134
|
const fontSizePx = ptToPx(style.fontSize ?? 11);
|
|
125
135
|
parts.push(`${fontSizePx}px`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ParagraphBlock, ParagraphMeasure, TextRun } from "../types.js";
|
|
2
2
|
import { FloatingImageZone } from "./floatingZones.js";
|
|
3
3
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
4
|
+
import { MIN_WRAP_SEGMENT_WIDTH, findClearLineY } from "./floatingLineClearance.js";
|
|
4
5
|
//#region src/layout-engine/measure/measureParagraph.d.ts
|
|
5
6
|
/**
|
|
6
7
|
* Options for paragraph measurement
|
|
@@ -16,26 +17,6 @@ type MeasureParagraphOptions = {
|
|
|
16
17
|
/** Header/footer tabs may be authored in the page margin, beyond body width. */
|
|
17
18
|
allowEndTabOverflow?: boolean;
|
|
18
19
|
};
|
|
19
|
-
/**
|
|
20
|
-
* Minimum horizontal room a line must offer before we treat it as usable for
|
|
21
|
-
* body text. Below this threshold the line is bumped past obstructing floats
|
|
22
|
-
* via `findClearLineY` instead of being rendered into the unusable sliver.
|
|
23
|
-
* Without this guard, a near-full-width float (e.g. floating table) produces
|
|
24
|
-
* a ~2px segment that collapses every wrap line to one glyph per row.
|
|
25
|
-
*/
|
|
26
|
-
declare const MIN_WRAP_SEGMENT_WIDTH = 24;
|
|
27
|
-
/**
|
|
28
|
-
* Find the next vertical position at or below `startY` where the available
|
|
29
|
-
* text width is at least `minWidth`. Used to skip lines past stacked floats
|
|
30
|
-
* when there is no horizontal room for meaningful text at the current Y.
|
|
31
|
-
*
|
|
32
|
-
* Returns `startY` if the current position already has enough room, otherwise
|
|
33
|
-
* the lowest `bottomY` of any zone currently obstructing the line. The caller
|
|
34
|
-
* is expected to re-query margins at the returned Y.
|
|
35
|
-
*
|
|
36
|
-
* Coordinates are absolute (i.e., already include any paragraphYOffset).
|
|
37
|
-
*/
|
|
38
|
-
declare function findClearLineY(startY: number, lineHeight: number, zones: FloatingImageZone[] | undefined, contentWidth: number, minWidth: number): number;
|
|
39
20
|
/**
|
|
40
21
|
* Measure a paragraph block and compute line breaks
|
|
41
22
|
*
|