@stll/folio-core 0.8.0 → 0.9.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/README.md +39 -0
- package/dist/ai-edits/apply.js +18 -4
- package/dist/ai-edits/headless.d.ts +31 -4
- package/dist/ai-edits/headless.js +148 -35
- package/dist/ai-edits/index.d.ts +3 -3
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/snapshot.js +40 -3
- package/dist/ai-edits/types.d.ts +2 -1
- package/dist/compat/eigenpal.d.ts +6 -4
- package/dist/compat/eigenpal.js +5 -3
- package/dist/controller/latestRequestGate.d.ts +13 -0
- package/dist/controller/latestRequestGate.js +16 -0
- package/dist/controller/layoutPipeline.js +12 -1
- package/dist/document-operations.d.ts +10 -3
- package/dist/document-operations.js +29 -14
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/commentParser.js +1 -1
- package/dist/docx/compatibility.d.ts +3 -7
- package/dist/docx/compatibility.js +0 -11
- package/dist/docx/encryption/agileDecryption.js +1 -1
- package/dist/docx/encryption/encryptionInfo.js +1 -1
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/fontTableParser.d.ts +6 -0
- package/dist/docx/fontTableParser.js +72 -0
- package/dist/docx/groupDrawingParser.js +7 -2
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/normalizeBaseDirection.js +1 -1
- package/dist/docx/numberingParser.js +1 -0
- package/dist/docx/paragraphParser.js +12 -1
- package/dist/docx/parser.d.ts +1 -1
- package/dist/docx/parser.js +12 -9
- package/dist/docx/rezip.js +70 -5
- package/dist/docx/runConsolidator.js +4 -0
- package/dist/docx/runParser.js +19 -1
- package/dist/docx/selectiveSave.js +3 -3
- package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
- package/dist/docx/serializer/fontTableSerializer.js +26 -0
- package/dist/docx/serializer/headerFooterSerializer.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +4 -0
- package/dist/docx/serializer/runSerializer.js +7 -0
- package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
- package/dist/docx/serializer/settingsSerializer.js +16 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
- package/dist/docx/serializer/stylesSerializer.js +56 -0
- package/dist/docx/serializer/tableSerializer.js +25 -26
- package/dist/docx/serializer/themeSerializer.d.ts +6 -0
- package/dist/docx/serializer/themeSerializer.js +36 -0
- package/dist/docx/settingsParser.js +46 -0
- package/dist/docx/styleParser.js +26 -1
- package/dist/docx/tableParser.js +11 -1
- package/dist/docx/vmlImageParser.js +1 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
- package/dist/i18n/messages/catalogs.gen.js +5910 -5910
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +6 -1
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +51 -14
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-engine/index.js +36 -28
- package/dist/layout-engine/measure/cache.js +11 -3
- package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
- package/dist/layout-engine/measure/index.d.ts +2 -1
- package/dist/layout-engine/measure/index.js +2 -1
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
- package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
- package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
- package/dist/layout-engine/measure/lineBreaks.js +11 -18
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +12 -6
- package/dist/layout-engine/measure/measureBlocks.d.ts +1 -2
- package/dist/layout-engine/measure/measureBlocks.js +6 -36
- package/dist/layout-engine/measure/measureContainer.js +32 -9
- package/dist/layout-engine/measure/measureHelpers.js +5 -2
- package/dist/layout-engine/measure/measureParagraph.js +335 -72
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
- package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
- package/dist/layout-engine/measure/measureWorker.js +8 -19
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
- package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
- package/dist/layout-engine/measure/tableCellFloating.js +37 -29
- package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
- package/dist/layout-engine/measure/tableCellFlow.js +73 -0
- package/dist/layout-engine/measure/tableCellGrid.js +1 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +6 -1
- package/dist/layout-engine/renderedBreakReconciliation.js +34 -11
- package/dist/layout-engine/tableRowBreak.d.ts +5 -2
- package/dist/layout-engine/tableRowBreak.js +62 -29
- package/dist/layout-engine/types.d.ts +40 -7
- package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
- package/dist/layout-painter/anchoredImagePosition.js +95 -0
- package/dist/layout-painter/borderStroke.d.ts +23 -0
- package/dist/layout-painter/borderStroke.js +39 -0
- package/dist/layout-painter/renderImage.d.ts +5 -4
- package/dist/layout-painter/renderImage.js +18 -4
- package/dist/layout-painter/renderPage.d.ts +2 -27
- package/dist/layout-painter/renderPage.js +13 -99
- package/dist/layout-painter/renderParagraph.js +41 -23
- package/dist/layout-painter/renderTable.d.ts +2 -0
- package/dist/layout-painter/renderTable.js +223 -43
- package/dist/managers/DocumentLoaderManager.d.ts +1 -1
- package/dist/managers/DocumentLoaderManager.js +2 -2
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +21 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +33 -18
- package/dist/prosemirror/conversion/toProseDoc.js +50 -18
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +7 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +6 -1
- package/dist/prosemirror/schema/nodes.d.ts +13 -11
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
- package/dist/prosemirror/utils/tabCalculator.js +12 -10
- package/dist/prosemirror/validation.js +4 -1
- package/dist/redline.js +13 -3
- package/dist/server.d.ts +6 -3
- package/dist/server.js +5 -2
- package/dist/style-sets/extract.d.ts +35 -0
- package/dist/style-sets/extract.js +123 -0
- package/dist/style-sets/stellaStyle.d.ts +13 -0
- package/dist/style-sets/stellaStyle.js +516 -0
- package/dist/style-sets/types.d.ts +31 -0
- package/dist/style-sets/types.js +5 -0
- package/dist/utils/createDocument.d.ts +12 -2
- package/dist/utils/createDocument.js +42 -31
- package/dist/utils/fontResolver.js +6 -6
- package/dist/utils/formatToStyle.js +1 -1
- package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
- package/dist/utils/paragraphFormattingMerge.js +14 -4
- package/package.json +5 -1
- package/dist/docx/capabilities.d.ts +0 -41
- package/dist/docx/capabilities.js +0 -322
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
2
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
2
|
+
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
3
|
+
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
3
4
|
import { buildRunFontStyle, ptToPx } from "./measureHelpers.js";
|
|
5
|
+
import "./lineBreakProvider.js";
|
|
4
6
|
import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
|
|
5
7
|
import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
|
|
6
8
|
import { hasCjk } from "../../utils/scriptSegments.js";
|
|
@@ -9,7 +11,7 @@ import { isFloatingImageRun } from "../types.js";
|
|
|
9
11
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
10
12
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
11
13
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
12
|
-
import { findWordBreaks, isBreakChar } from "./lineBreaks.js";
|
|
14
|
+
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
13
15
|
//#region src/layout-engine/measure/measureParagraph.ts
|
|
14
16
|
/**
|
|
15
17
|
* Paragraph measurement module
|
|
@@ -22,9 +24,11 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
22
24
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
25
|
const WIDTH_TOLERANCE = .5;
|
|
24
26
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
27
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .1;
|
|
28
|
+
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
29
|
+
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .23;
|
|
25
30
|
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
26
31
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
27
|
-
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .02;
|
|
28
32
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
29
33
|
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
30
34
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
@@ -32,18 +36,20 @@ const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
|
32
36
|
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
33
37
|
* Returns the number of characters that fit (at least 1 if `forceMin` is true).
|
|
34
38
|
*/
|
|
35
|
-
function findMaxFittingLength(text, style, maxWidth, forceMin = false) {
|
|
36
|
-
|
|
37
|
-
let
|
|
39
|
+
function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
40
|
+
const boundaries = findGraphemeBreaks(text, policy);
|
|
41
|
+
let lo = 0;
|
|
42
|
+
let hi = boundaries.length - 1;
|
|
38
43
|
let best = 0;
|
|
39
44
|
while (lo <= hi) {
|
|
40
45
|
const mid = Math.floor((lo + hi) / 2);
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const boundary = boundaries[mid];
|
|
47
|
+
if (measureTextWidth(text.slice(0, boundary), style) <= maxWidth) {
|
|
48
|
+
best = boundary;
|
|
43
49
|
lo = mid + 1;
|
|
44
50
|
} else hi = mid - 1;
|
|
45
51
|
}
|
|
46
|
-
return forceMin && best === 0 ?
|
|
52
|
+
return forceMin && best === 0 ? boundaries.at(0) ?? 0 : best;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Extract FontStyle from a run that carries RunFormatting (text, tab, or
|
|
@@ -54,6 +60,31 @@ function findMaxFittingLength(text, style, maxWidth, forceMin = false) {
|
|
|
54
60
|
function runToFontStyle(run) {
|
|
55
61
|
return buildRunFontStyle(run, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
|
56
62
|
}
|
|
63
|
+
function languageMatches(ruleLanguage, locale) {
|
|
64
|
+
if (!ruleLanguage) return true;
|
|
65
|
+
if (!locale) return false;
|
|
66
|
+
const rule = ruleLanguage.toLowerCase();
|
|
67
|
+
const active = locale.toLowerCase();
|
|
68
|
+
return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
|
|
69
|
+
}
|
|
70
|
+
function lineBreakPolicy(block, run) {
|
|
71
|
+
const language = run.language;
|
|
72
|
+
let locale = language?.val;
|
|
73
|
+
if (hasCjk(run.text)) locale = language?.eastAsia ?? language?.val;
|
|
74
|
+
else if (run.rtl) locale = language?.bidi ?? language?.val;
|
|
75
|
+
const rules = block.attrs?.lineBreakRules;
|
|
76
|
+
const before = rules?.noLineBreaksBefore;
|
|
77
|
+
const after = rules?.noLineBreaksAfter;
|
|
78
|
+
return {
|
|
79
|
+
...locale ? { locale } : {},
|
|
80
|
+
...block.attrs?.kinsoku !== void 0 ? { kinsoku: block.attrs.kinsoku } : {},
|
|
81
|
+
...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
|
|
82
|
+
...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
|
|
83
|
+
...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
|
|
84
|
+
...block.attrs?.automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: block.attrs.automaticHyphenation.doNotHyphenateCaps } : {},
|
|
85
|
+
...run.allCaps === true ? { renderedAllCaps: true } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
57
88
|
/**
|
|
58
89
|
* Line-HEIGHT style for a text run. Word derives a CJK line's height from an
|
|
59
90
|
* East-Asian face, not the run's ascii font: real documents routinely put CJK
|
|
@@ -331,34 +362,63 @@ function uppercaseLetterRatio(text) {
|
|
|
331
362
|
}
|
|
332
363
|
return letters === 0 ? 0 : uppercase / letters;
|
|
333
364
|
}
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
if (totalSpaces === 0) return tolerance;
|
|
337
|
-
return Math.max(JUSTIFY_SHRINK_TOLERANCE_RATIO, tolerance * (regularSpaceCount / totalSpaces));
|
|
338
|
-
}
|
|
339
|
-
function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonBreakingSpaceCount) {
|
|
365
|
+
function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
366
|
+
if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
|
|
340
367
|
if (block.attrs?.listMarker !== void 0) {
|
|
341
368
|
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
342
|
-
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ?
|
|
369
|
+
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? {
|
|
370
|
+
type: "space",
|
|
371
|
+
ratio: JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO
|
|
372
|
+
} : {
|
|
373
|
+
type: "width",
|
|
374
|
+
ratio: JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO
|
|
375
|
+
};
|
|
343
376
|
const left = block.attrs.indent?.left ?? 0;
|
|
344
|
-
if (hanging > 0 && left > hanging) return
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
377
|
+
if (hanging > 0 && left > hanging) return {
|
|
378
|
+
type: "width",
|
|
379
|
+
ratio: JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
type: "space",
|
|
383
|
+
ratio: JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO
|
|
384
|
+
};
|
|
350
385
|
}
|
|
351
386
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
387
|
+
if (isFirstLine && profile.hasTabRuns && (block.attrs?.indent?.hanging ?? 0) > 0) return {
|
|
388
|
+
type: "width",
|
|
389
|
+
ratio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
390
|
+
};
|
|
391
|
+
if (!isFirstLine && profile.hasTabRuns && !hasTabStops) return {
|
|
392
|
+
type: "width",
|
|
393
|
+
ratio: JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO
|
|
394
|
+
};
|
|
395
|
+
if (profile.hasTabRuns && (isFirstLine || hasTabStops)) return {
|
|
396
|
+
type: "width",
|
|
397
|
+
ratio: (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
398
|
+
};
|
|
399
|
+
if (profile.uppercaseRatio > ALL_CAPS_RATIO_THRESHOLD) return {
|
|
400
|
+
type: "width",
|
|
401
|
+
ratio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
402
|
+
};
|
|
403
|
+
return {
|
|
404
|
+
type: "space",
|
|
405
|
+
ratio: JUSTIFY_SPACE_CONTRACTION_RATIO
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function compressibleSpaceWidth(text, style) {
|
|
409
|
+
const count = countCompressibleSpaces(text);
|
|
410
|
+
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
411
|
+
}
|
|
412
|
+
function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
|
|
413
|
+
if (strategy.type === "rounding") return WIDTH_TOLERANCE;
|
|
414
|
+
if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
|
|
415
|
+
return Math.max(WIDTH_TOLERANCE, (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio);
|
|
416
|
+
}
|
|
417
|
+
function isShallowFullHangingListContinuation(block, isFirstLine) {
|
|
418
|
+
if (isFirstLine || block.attrs?.listMarker === void 0) return false;
|
|
419
|
+
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
420
|
+
const left = block.attrs.indent?.left ?? 0;
|
|
421
|
+
return hanging > 0 && hanging < DEFAULT_LIST_HANGING_INDENT_PX && Math.abs(left - hanging) <= WIDTH_TOLERANCE;
|
|
362
422
|
}
|
|
363
423
|
function trimTrailingSpacesAndTabs(text) {
|
|
364
424
|
let end = text.length;
|
|
@@ -369,12 +429,20 @@ function trimTrailingSpacesAndTabs(text) {
|
|
|
369
429
|
}
|
|
370
430
|
return text.slice(0, end);
|
|
371
431
|
}
|
|
432
|
+
function trailingHangingPunctuationWidth(text, style, policy, enabled) {
|
|
433
|
+
if (!enabled || text.length === 0) return 0;
|
|
434
|
+
const lastStart = findGraphemeBreaks(text, policy).at(-2) ?? 0;
|
|
435
|
+
const lastGrapheme = text.slice(lastStart);
|
|
436
|
+
if (!isHangingPunctuation(lastGrapheme, policy)) return 0;
|
|
437
|
+
return measureTextWidth(lastGrapheme, style);
|
|
438
|
+
}
|
|
372
439
|
/**
|
|
373
440
|
* Width of the unbreakable text glued to the end of each run.
|
|
374
441
|
* A run boundary is not itself a wrap opportunity: adjacent note markers and
|
|
375
442
|
* format-only word splits must wrap as one cluster.
|
|
376
443
|
*/
|
|
377
|
-
function computeTrailingGlueWidths(
|
|
444
|
+
function computeTrailingGlueWidths(block) {
|
|
445
|
+
const runs = block.runs;
|
|
378
446
|
const widths = Array.from({ length: runs.length }, () => 0);
|
|
379
447
|
for (let index = runs.length - 1; index >= 0; index--) {
|
|
380
448
|
const nextRun = runs[index + 1];
|
|
@@ -384,19 +452,112 @@ function computeTrailingGlueWidths(runs) {
|
|
|
384
452
|
widths[index] = widths[index + 1] ?? 0;
|
|
385
453
|
continue;
|
|
386
454
|
}
|
|
387
|
-
if (isSpaceOrTab(text
|
|
455
|
+
if (isSpaceOrTab(text.at(0))) continue;
|
|
388
456
|
const style = runToFontStyle(nextRun);
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
457
|
+
const firstBreak = findWordBreaks(text, lineBreakPolicy(block, nextRun)).at(0);
|
|
458
|
+
widths[index] = measureTextWidth(firstBreak === void 0 ? text : trimTrailingSpacesAndTabs(text.slice(0, firstBreak)), style) + (firstBreak === void 0 ? widths[index + 1] ?? 0 : 0);
|
|
459
|
+
}
|
|
460
|
+
return widths;
|
|
461
|
+
}
|
|
462
|
+
function computeProtectedCrossRunGlueWidths(block) {
|
|
463
|
+
const widths = Array.from({ length: block.runs.length }, () => 0);
|
|
464
|
+
for (let index = 0; index < block.runs.length; index++) {
|
|
465
|
+
const run = block.runs[index];
|
|
466
|
+
if (!run || !isTextRun(run)) continue;
|
|
467
|
+
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
468
|
+
const token = trailing?.[1];
|
|
469
|
+
const policy = lineBreakPolicy(block, run);
|
|
470
|
+
if (!token || token.length !== 1 || !policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
471
|
+
let separator = trailing[2] ?? "";
|
|
472
|
+
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
473
|
+
let followingWord = "";
|
|
474
|
+
let unseparatedFollowingText = false;
|
|
475
|
+
for (let nextIndex = index + 1; nextIndex < block.runs.length; nextIndex++) {
|
|
476
|
+
const nextRun = block.runs[nextIndex];
|
|
477
|
+
if (!nextRun || !isTextRun(nextRun)) break;
|
|
478
|
+
const text = nextRun.text ?? "";
|
|
479
|
+
let consumed = "";
|
|
480
|
+
for (const char of text) {
|
|
481
|
+
const isWhitespace = /\s/u.test(char);
|
|
482
|
+
if (followingWord.length > 0 && isWhitespace) break;
|
|
483
|
+
if (separator.length === 0 && !isWhitespace) {
|
|
484
|
+
unseparatedFollowingText = true;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
consumed += char;
|
|
488
|
+
if (isWhitespace) separator += char;
|
|
489
|
+
else followingWord += char;
|
|
490
|
+
}
|
|
491
|
+
if (consumed.length > 0) glueWidth += measureTextWidth(consumed, runToFontStyle(nextRun));
|
|
492
|
+
if (unseparatedFollowingText) break;
|
|
493
|
+
if (followingWord.length > 0 && consumed.length < text.length) break;
|
|
393
494
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
495
|
+
if (unseparatedFollowingText || separator.length === 0 || followingWord.length === 0) continue;
|
|
496
|
+
const boundary = token.length + separator.length;
|
|
497
|
+
if (!findWordBreaks(token + separator + followingWord, policy).includes(boundary)) widths[index] = glueWidth;
|
|
397
498
|
}
|
|
398
499
|
return widths;
|
|
399
500
|
}
|
|
501
|
+
/** Collect one lexical word continued through adjacent formatting runs. */
|
|
502
|
+
function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
503
|
+
const segments = [];
|
|
504
|
+
let text = "";
|
|
505
|
+
let width = 0;
|
|
506
|
+
for (let runIndex = startRunIndex; runIndex < block.runs.length; runIndex++) {
|
|
507
|
+
const run = block.runs[runIndex];
|
|
508
|
+
if (!run || !isTextRun(run)) break;
|
|
509
|
+
const start = runIndex === startRunIndex ? startChar : 0;
|
|
510
|
+
const remainder = run.text.slice(start);
|
|
511
|
+
if (remainder.length === 0) continue;
|
|
512
|
+
if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
|
|
513
|
+
const remainingBudget = 256 - text.length;
|
|
514
|
+
const boundedRemainder = remainder.slice(0, remainingBudget + 1);
|
|
515
|
+
const firstBreak = findWordBreaks(boundedRemainder, lineBreakPolicy(block, run)).at(0);
|
|
516
|
+
const segmentText = trimTrailingSpacesAndTabs(firstBreak === void 0 ? boundedRemainder : boundedRemainder.slice(0, firstBreak));
|
|
517
|
+
if (segmentText.length === 0) break;
|
|
518
|
+
if (segmentText.length > remainingBudget) return;
|
|
519
|
+
const style = runToFontStyle(run);
|
|
520
|
+
segments.push({
|
|
521
|
+
runIndex,
|
|
522
|
+
startChar: start,
|
|
523
|
+
text: segmentText,
|
|
524
|
+
run,
|
|
525
|
+
style
|
|
526
|
+
});
|
|
527
|
+
text += segmentText;
|
|
528
|
+
width += measureTextWidth(segmentText, style);
|
|
529
|
+
if (firstBreak !== void 0) break;
|
|
530
|
+
}
|
|
531
|
+
if (segments.length < 2) return;
|
|
532
|
+
const firstRun = segments[0]?.run;
|
|
533
|
+
if (!firstRun) return;
|
|
534
|
+
return {
|
|
535
|
+
text,
|
|
536
|
+
width,
|
|
537
|
+
breaks: findHyphenationBreaks(text, lineBreakPolicy(block, firstRun)),
|
|
538
|
+
segments
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function measureCrossRunPrefix(word, breakOffset) {
|
|
542
|
+
let width = 0;
|
|
543
|
+
let remaining = breakOffset;
|
|
544
|
+
const measuredSegments = [];
|
|
545
|
+
for (const segment of word.segments) {
|
|
546
|
+
const length = Math.min(remaining, segment.text.length);
|
|
547
|
+
if (length > 0) {
|
|
548
|
+
width += measureTextWidth(segment.text.slice(0, length), segment.style);
|
|
549
|
+
measuredSegments.push(segment);
|
|
550
|
+
}
|
|
551
|
+
if (remaining <= segment.text.length) return {
|
|
552
|
+
width,
|
|
553
|
+
endRunIndex: segment.runIndex,
|
|
554
|
+
endChar: segment.startChar + remaining,
|
|
555
|
+
hyphenStyle: segment.style,
|
|
556
|
+
segments: measuredSegments
|
|
557
|
+
};
|
|
558
|
+
remaining -= segment.text.length;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
400
561
|
/**
|
|
401
562
|
* Minimum horizontal room a line must offer before we treat it as usable for
|
|
402
563
|
* body text. Below this threshold the line is bumped past obstructing floats
|
|
@@ -446,6 +607,12 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
446
607
|
const attrs = block.attrs;
|
|
447
608
|
const spacing = attrs?.spacing;
|
|
448
609
|
const isJustifiedParagraph = attrs?.alignment === "justify";
|
|
610
|
+
const justificationProfile = {
|
|
611
|
+
hasTabRuns: isJustifiedParagraph && runs.some(isTabRun),
|
|
612
|
+
uppercaseRatio: isJustifiedParagraph ? uppercaseLetterRatio(runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) : 0
|
|
613
|
+
};
|
|
614
|
+
const firstLineJustifyFitStrategy = resolveJustifyFitStrategy(block, true, justificationProfile);
|
|
615
|
+
const continuationJustifyFitStrategy = resolveJustifyFitStrategy(block, false, justificationProfile);
|
|
449
616
|
const floatingZones = options?.floatingZones;
|
|
450
617
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
451
618
|
const indent = attrs?.indent;
|
|
@@ -476,6 +643,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
476
643
|
const firstLineFloatingMargins = getFloatingMargins(cumulativeHeight, estimatedFirstLineHeight, floatingZones, paragraphYOffset);
|
|
477
644
|
const firstLineWidth = Math.max(1, getFloatingAvailableWidth(firstLineFloatingMargins, baseFirstLineWidth));
|
|
478
645
|
const lines = [];
|
|
646
|
+
let consecutiveHyphenatedLines = 0;
|
|
479
647
|
if (runs.length === 0) {
|
|
480
648
|
if (attrs?.suppressEmptyParagraphHeight) {
|
|
481
649
|
lines.push({
|
|
@@ -534,7 +702,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
534
702
|
totalHeight
|
|
535
703
|
};
|
|
536
704
|
}
|
|
537
|
-
const trailingGlueWidths = computeTrailingGlueWidths(
|
|
705
|
+
const trailingGlueWidths = computeTrailingGlueWidths(block);
|
|
706
|
+
const protectedCrossRunGlueWidths = computeProtectedCrossRunGlueWidths(block);
|
|
538
707
|
let currentLine = {
|
|
539
708
|
fromRun: 0,
|
|
540
709
|
fromChar: 0,
|
|
@@ -542,11 +711,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
542
711
|
toChar: 0,
|
|
543
712
|
width: 0,
|
|
544
713
|
trailingWhitespaceWidth: 0,
|
|
545
|
-
|
|
546
|
-
nonBreakingSpaceCount: 0,
|
|
714
|
+
regularSpaceWidth: 0,
|
|
547
715
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
548
716
|
maxFontMetrics: null,
|
|
549
717
|
maxImageHeightPx: 0,
|
|
718
|
+
maxExactImageHeightPx: 0,
|
|
550
719
|
maxMathHeightPx: 0,
|
|
551
720
|
availableWidth: firstLineWidth,
|
|
552
721
|
leftOffset: firstLineFloatingMargins.leftMargin,
|
|
@@ -566,9 +735,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
566
735
|
if (inlineObjectHeight > finalTypography.lineHeight) {
|
|
567
736
|
const objectHeight = inlineObjectHeight;
|
|
568
737
|
const buffer = finalTypography.descent;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
finalTypography.
|
|
738
|
+
const soleRun = line.fromRun === line.toRun ? runs[line.fromRun] : void 0;
|
|
739
|
+
if (line.maxExactImageHeightPx >= objectHeight) {
|
|
740
|
+
finalTypography.lineHeight = objectHeight;
|
|
741
|
+
finalTypography.ascent = objectHeight;
|
|
742
|
+
finalTypography.descent = 0;
|
|
743
|
+
return finalTypography;
|
|
744
|
+
}
|
|
745
|
+
if (soleRun && isImageRun(soleRun)) {
|
|
746
|
+
finalTypography.lineHeight = objectHeight;
|
|
747
|
+
finalTypography.ascent = objectHeight;
|
|
748
|
+
finalTypography.descent = 0;
|
|
572
749
|
} else {
|
|
573
750
|
finalTypography.lineHeight = objectHeight + buffer;
|
|
574
751
|
finalTypography.ascent = objectHeight;
|
|
@@ -598,7 +775,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
598
775
|
toRun: currentLine.toRun,
|
|
599
776
|
toChar: currentLine.toChar,
|
|
600
777
|
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
601
|
-
...finalTypography
|
|
778
|
+
...finalTypography,
|
|
779
|
+
...currentLine.discretionaryHyphen ? { discretionaryHyphen: currentLine.discretionaryHyphen } : {}
|
|
602
780
|
};
|
|
603
781
|
if (currentLine.leftOffset > 0) line.leftOffset = currentLine.leftOffset;
|
|
604
782
|
if (currentLine.rightOffset > 0) line.rightOffset = currentLine.rightOffset;
|
|
@@ -607,6 +785,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
607
785
|
pendingFloatSkip = 0;
|
|
608
786
|
}
|
|
609
787
|
lines.push(line);
|
|
788
|
+
consecutiveHyphenatedLines = currentLine.discretionaryHyphen ? consecutiveHyphenatedLines + 1 : 0;
|
|
610
789
|
cumulativeHeight += finalTypography.lineHeight;
|
|
611
790
|
};
|
|
612
791
|
/**
|
|
@@ -625,11 +804,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
625
804
|
toChar: charIndex,
|
|
626
805
|
width: 0,
|
|
627
806
|
trailingWhitespaceWidth: 0,
|
|
628
|
-
|
|
629
|
-
nonBreakingSpaceCount: 0,
|
|
807
|
+
regularSpaceWidth: 0,
|
|
630
808
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
631
809
|
maxFontMetrics: null,
|
|
632
810
|
maxImageHeightPx: 0,
|
|
811
|
+
maxExactImageHeightPx: 0,
|
|
633
812
|
maxMathHeightPx: 0,
|
|
634
813
|
availableWidth: adjustedWidth,
|
|
635
814
|
leftOffset: floatingMargins.leftMargin,
|
|
@@ -651,7 +830,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
651
830
|
const metrics = getFontMetrics(style);
|
|
652
831
|
if (metrics.singleLineRatio > currentLine.maxFontMetrics.singleLineRatio) currentLine.maxFontMetrics = metrics;
|
|
653
832
|
};
|
|
833
|
+
let crossRunResume;
|
|
654
834
|
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
835
|
+
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
655
836
|
const run = runs[runIndex];
|
|
656
837
|
if (isLineBreakRun(run)) {
|
|
657
838
|
currentLine.toRun = runIndex;
|
|
@@ -713,6 +894,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
713
894
|
if (currentLine.width > 0 && currentLine.width + imageWidth > currentLine.availableWidth + WIDTH_TOLERANCE) startNewLine(runIndex, 0);
|
|
714
895
|
const imageFootprintPx = imageHeight + (run.distTop ?? 0) + (run.distBottom ?? 0);
|
|
715
896
|
if (imageFootprintPx > currentLine.maxImageHeightPx) currentLine.maxImageHeightPx = imageFootprintPx;
|
|
897
|
+
if (run.exactLineHeight === true && imageFootprintPx > currentLine.maxExactImageHeightPx) currentLine.maxExactImageHeightPx = imageFootprintPx;
|
|
716
898
|
currentLine.width += imageWidth;
|
|
717
899
|
currentLine.trailingWhitespaceWidth = 0;
|
|
718
900
|
currentLine.toRun = runIndex;
|
|
@@ -724,11 +906,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
724
906
|
const style = buildRunFontStyle(run, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
|
725
907
|
updateMaxFont(style);
|
|
726
908
|
const fieldWidth = measureTextWidth(fallback, style);
|
|
727
|
-
|
|
909
|
+
const fieldSpaceWidth = compressibleSpaceWidth(fallback, style);
|
|
910
|
+
const fieldTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, lines.length === 0 ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, fieldSpaceWidth) : WIDTH_TOLERANCE;
|
|
911
|
+
if (currentLine.width > 0 && currentLine.width + fieldWidth > currentLine.availableWidth + fieldTolerance) {
|
|
728
912
|
startNewLine(runIndex, 0);
|
|
729
913
|
updateMaxFont(style);
|
|
730
914
|
}
|
|
731
915
|
currentLine.width += fieldWidth;
|
|
916
|
+
currentLine.regularSpaceWidth += fieldSpaceWidth;
|
|
732
917
|
currentLine.trailingWhitespaceWidth = 0;
|
|
733
918
|
currentLine.toRun = runIndex;
|
|
734
919
|
currentLine.toChar = 1;
|
|
@@ -759,6 +944,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
759
944
|
const textRun = run;
|
|
760
945
|
const text = textRun.text;
|
|
761
946
|
const style = runToFontStyle(textRun);
|
|
947
|
+
const breakPolicy = lineBreakPolicy(block, textRun);
|
|
762
948
|
const lineHeightStyle = cjkLineHeightStyle(textRun, style);
|
|
763
949
|
updateMaxFont(lineHeightStyle);
|
|
764
950
|
if (!text || text.length === 0) {
|
|
@@ -766,8 +952,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
766
952
|
currentLine.toChar = 0;
|
|
767
953
|
continue;
|
|
768
954
|
}
|
|
769
|
-
const wordBreaks = findWordBreaks(text);
|
|
770
|
-
let charIndex = 0;
|
|
955
|
+
const wordBreaks = findWordBreaks(text, breakPolicy);
|
|
956
|
+
let charIndex = crossRunResume?.runIndex === runIndex ? crossRunResume.charIndex : 0;
|
|
957
|
+
if (crossRunResume?.runIndex === runIndex) crossRunResume = void 0;
|
|
958
|
+
let activeHyphenationWord;
|
|
771
959
|
while (charIndex < text.length) {
|
|
772
960
|
let nextBreak = text.length;
|
|
773
961
|
for (const breakPoint of wordBreaks) if (breakPoint > charIndex) {
|
|
@@ -778,29 +966,103 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
778
966
|
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
779
967
|
const wordWidth = measureTextWidth(measuredWord, style);
|
|
780
968
|
const fullWordWidth = measureTextWidth(word, style);
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
|
|
969
|
+
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, block.attrs?.overflowPunctuation);
|
|
970
|
+
const isFirstLine = lines.length === 0;
|
|
971
|
+
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
972
|
+
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, isFirstLine ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
973
|
+
const automaticHyphenation = block.attrs?.automaticHyphenation;
|
|
974
|
+
const consecutiveLineLimit = automaticHyphenation?.consecutiveLineLimit ?? 0;
|
|
975
|
+
const mayHyphenateLine = automaticHyphenation?.enabled === true && block.attrs?.suppressAutoHyphens !== true && (consecutiveLineLimit === 0 || consecutiveHyphenatedLines < consecutiveLineLimit);
|
|
976
|
+
const isRunTail = nextBreak === text.length;
|
|
977
|
+
const crossRunWord = mayHyphenateLine && isRunTail && word.length > 0 && !isBreakChar(word.at(-1)) ? collectCrossRunWord({
|
|
978
|
+
block,
|
|
979
|
+
startRunIndex: runIndex,
|
|
980
|
+
startChar: charIndex
|
|
981
|
+
}) : void 0;
|
|
982
|
+
if (mayHyphenateLine && crossRunWord === void 0 && measuredWord.length > 0 && (activeHyphenationWord === void 0 || charIndex < activeHyphenationWord.start || charIndex >= activeHyphenationWord.end)) {
|
|
983
|
+
const fullWordEnd = charIndex + measuredWord.length;
|
|
984
|
+
const fullWordText = text.slice(charIndex, fullWordEnd);
|
|
985
|
+
activeHyphenationWord = {
|
|
986
|
+
start: charIndex,
|
|
987
|
+
end: fullWordEnd,
|
|
988
|
+
text: fullWordText,
|
|
989
|
+
breaks: findHyphenationBreaks(fullWordText, breakPolicy)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const overflowsCurrentLine = wordWidth > 0 && currentLine.width + wordWidth > currentLine.availableWidth + widthTolerance;
|
|
993
|
+
if (mayHyphenateLine && overflowsCurrentLine && activeHyphenationWord) {
|
|
994
|
+
const consumed = charIndex - activeHyphenationWord.start;
|
|
995
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
996
|
+
const hyphenWidth = measureTextWidth("-", style);
|
|
997
|
+
let fittingBreak;
|
|
998
|
+
for (const breakOffset of activeHyphenationWord.breaks) {
|
|
999
|
+
if (breakOffset <= consumed || breakOffset >= activeHyphenationWord.text.length) continue;
|
|
1000
|
+
if (measureTextWidth(activeHyphenationWord.text.slice(consumed, breakOffset), style) + hyphenWidth <= spaceLeft) fittingBreak = breakOffset;
|
|
1001
|
+
}
|
|
1002
|
+
if (fittingBreak !== void 0) {
|
|
1003
|
+
const absoluteBreak = activeHyphenationWord.start + fittingBreak;
|
|
1004
|
+
const prefix = text.slice(charIndex, absoluteBreak);
|
|
1005
|
+
currentLine.width += measureTextWidth(prefix, style) + hyphenWidth;
|
|
1006
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
1007
|
+
currentLine.toRun = runIndex;
|
|
1008
|
+
currentLine.toChar = absoluteBreak;
|
|
1009
|
+
currentLine.discretionaryHyphen = { runIndex };
|
|
1010
|
+
startNewLine(runIndex, absoluteBreak);
|
|
1011
|
+
updateMaxFont(lineHeightStyle);
|
|
1012
|
+
charIndex = absoluteBreak;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (crossRunWord && currentLine.width + crossRunWord.width > currentLine.availableWidth + widthTolerance) {
|
|
1017
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
1018
|
+
let fittingPrefix;
|
|
1019
|
+
for (let breakIndex = crossRunWord.breaks.length - 1; breakIndex >= 0; breakIndex -= 1) {
|
|
1020
|
+
const breakOffset = crossRunWord.breaks[breakIndex];
|
|
1021
|
+
if (breakOffset === void 0) continue;
|
|
1022
|
+
if (breakOffset <= 0 || breakOffset >= crossRunWord.text.length) continue;
|
|
1023
|
+
const prefix = measureCrossRunPrefix(crossRunWord, breakOffset);
|
|
1024
|
+
if (prefix && prefix.width + measureTextWidth("-", prefix.hyphenStyle) <= spaceLeft) {
|
|
1025
|
+
fittingPrefix = prefix;
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (fittingPrefix) {
|
|
1030
|
+
for (const segment of fittingPrefix.segments) updateMaxFont(cjkLineHeightStyle(segment.run, segment.style));
|
|
1031
|
+
currentLine.width += fittingPrefix.width + measureTextWidth("-", fittingPrefix.hyphenStyle);
|
|
1032
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
1033
|
+
currentLine.toRun = fittingPrefix.endRunIndex;
|
|
1034
|
+
currentLine.toChar = fittingPrefix.endChar;
|
|
1035
|
+
currentLine.discretionaryHyphen = { runIndex: fittingPrefix.endRunIndex };
|
|
1036
|
+
startNewLine(fittingPrefix.endRunIndex, fittingPrefix.endChar);
|
|
1037
|
+
if (fittingPrefix.endRunIndex === runIndex) {
|
|
1038
|
+
updateMaxFont(lineHeightStyle);
|
|
1039
|
+
charIndex = fittingPrefix.endChar;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
crossRunResume = {
|
|
1043
|
+
runIndex: fittingPrefix.endRunIndex,
|
|
1044
|
+
charIndex: fittingPrefix.endChar
|
|
1045
|
+
};
|
|
1046
|
+
charIndex = text.length;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (wordWidth - hangingPunctuationWidth > currentLine.availableWidth + widthTolerance) {
|
|
785
1051
|
let chunkStart = 0;
|
|
786
1052
|
while (chunkStart < measuredWord.length) {
|
|
787
1053
|
const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
|
|
788
|
-
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft);
|
|
1054
|
+
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft, currentLine.width === 0, breakPolicy);
|
|
789
1055
|
if (bestEnd === 0) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
bestEnd = 1;
|
|
1056
|
+
startNewLine(runIndex, charIndex + chunkStart);
|
|
1057
|
+
updateMaxFont(lineHeightStyle);
|
|
1058
|
+
continue;
|
|
796
1059
|
}
|
|
797
1060
|
const chunkEnd = chunkStart + bestEnd;
|
|
798
1061
|
const chunk = measuredWord.slice(chunkStart, chunkEnd);
|
|
799
1062
|
const chunkWidth = measureTextWidth(chunk, style);
|
|
800
1063
|
currentLine.width += chunkWidth;
|
|
801
1064
|
currentLine.trailingWhitespaceWidth = chunkWidth - measureTextWidth(trimTrailingSpacesAndTabs(chunk), style);
|
|
802
|
-
currentLine.
|
|
803
|
-
currentLine.nonBreakingSpaceCount += chunk.split("\xA0").length - 1;
|
|
1065
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(chunk, style);
|
|
804
1066
|
currentLine.toRun = runIndex;
|
|
805
1067
|
currentLine.toChar = charIndex + chunkEnd;
|
|
806
1068
|
chunkStart = chunkEnd;
|
|
@@ -812,23 +1074,24 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
812
1074
|
const trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
813
1075
|
currentLine.width += trailingWhitespaceWidth;
|
|
814
1076
|
currentLine.trailingWhitespaceWidth = trailingWhitespaceWidth;
|
|
815
|
-
currentLine.
|
|
816
|
-
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
1077
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(word, style);
|
|
817
1078
|
currentLine.toChar = nextBreak;
|
|
818
1079
|
charIndex = nextBreak;
|
|
819
1080
|
continue;
|
|
820
1081
|
}
|
|
821
|
-
const
|
|
1082
|
+
const wordTail = word.at(-1);
|
|
1083
|
+
const protectedGlueWidth = protectedCrossRunGlueWidths[runIndex] ?? 0;
|
|
1084
|
+
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!isBreakChar(wordTail) || protectedGlueWidth > 0) ? Math.max(trailingGlueWidths[runIndex] ?? 0, protectedGlueWidth) : 0;
|
|
822
1085
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
823
|
-
|
|
1086
|
+
const hangingAllowance = glueWidth === 0 ? hangingPunctuationWidth : 0;
|
|
1087
|
+
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance + hangingAllowance) {
|
|
824
1088
|
startNewLine(runIndex, charIndex);
|
|
825
1089
|
updateMaxFont(lineHeightStyle);
|
|
826
1090
|
}
|
|
827
1091
|
currentLine.width += fullWordWidth;
|
|
828
1092
|
const wordTrailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
829
1093
|
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
830
|
-
currentLine.
|
|
831
|
-
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
1094
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(word, style);
|
|
832
1095
|
currentLine.toRun = runIndex;
|
|
833
1096
|
currentLine.toChar = nextBreak;
|
|
834
1097
|
charIndex = nextBreak;
|
|
@@ -27,7 +27,8 @@ type FontStyle = {
|
|
|
27
27
|
letterSpacing?: number;
|
|
28
28
|
textTransform?: "uppercase";
|
|
29
29
|
fontVariant?: "small-caps";
|
|
30
|
-
horizontalScale?: number;
|
|
30
|
+
horizontalScale?: number; /** Enable pair kerning for runs whose authored threshold is met. */
|
|
31
|
+
kerning?: boolean;
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
33
34
|
* Typography metrics for a font
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MeasureWorkerRequest, MeasureWorkerResponse } from "./measureWorkerProtocol.js";
|
|
1
|
+
import { MeasureRequestEntry, MeasureWorkerRequest, MeasureWorkerResponse } from "./measureWorkerProtocol.js";
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/measure/measureWorker.d.ts
|
|
4
4
|
/**
|
|
@@ -32,7 +32,7 @@ declare function __setMeasureWorkerTransport(factory: TransportFactory | null):
|
|
|
32
32
|
* `OffscreenCanvas`/`Worker`, or when the proxy has been disabled by
|
|
33
33
|
* a previous error.
|
|
34
34
|
*/
|
|
35
|
-
declare function prefetchMeasurement(
|
|
35
|
+
declare function prefetchMeasurement(request: MeasureRequestEntry): void;
|
|
36
36
|
declare function canPrefetchMeasurement(): boolean;
|
|
37
37
|
/**
|
|
38
38
|
* Test-only: synchronously drain the pending queue. The caller is
|