@stll/folio-core 0.8.0 → 0.10.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 +73 -6
- package/dist/ai-edits/headless.js +299 -60
- 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 +13 -1
- package/dist/document-operations.d.ts +20 -4
- package/dist/document-operations.js +36 -15
- package/dist/document-stories.d.ts +10 -0
- package/dist/document-stories.js +27 -0
- 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/corePropertiesParser.d.ts +8 -0
- package/dist/docx/corePropertiesParser.js +53 -0
- 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/index.d.ts +2 -1
- package/dist/docx/index.js +2 -1
- package/dist/docx/metadataPrivacy.d.ts +40 -0
- package/dist/docx/metadataPrivacy.js +131 -0
- 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 +16 -10
- package/dist/docx/rezip.d.ts +5 -4
- package/dist/docx/rezip.js +76 -13
- 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 +52 -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 +6 -3
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
- 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 +332 -73
- 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 +51 -37
- 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 +43 -8
- 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 +22 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
- package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +34 -18
- package/dist/prosemirror/conversion/toProseDoc.js +81 -77
- 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 +3 -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 +15 -12
- 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.d.ts +26 -11
- package/dist/redline.js +107 -64
- package/dist/server.d.ts +9 -5
- package/dist/server.js +8 -4
- 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/dist/version-comparison.d.ts +65 -11
- package/dist/version-comparison.js +187 -29
- package/package.json +5 -1
- package/dist/docx/capabilities.d.ts +0 -41
- package/dist/docx/capabilities.js +0 -322
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
2
1
|
import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
|
|
3
|
-
import {
|
|
2
|
+
import { getFontMetrics, measureRun, measureTextWidth } from "./measureProvider.js";
|
|
3
|
+
import { countCompressibleSpaces } from "./textMeasurementPolicy.js";
|
|
4
|
+
import { buildRunFontStyle, ptToPx, twipsToPx } 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";
|
|
7
9
|
import { measuredLineAdvance } from "../lineFlow.js";
|
|
8
10
|
import { isFloatingImageRun } from "../types.js";
|
|
11
|
+
import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
|
|
9
12
|
import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
|
|
10
13
|
import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
|
|
11
14
|
import { getListMarkerInlineWidth } from "./listMarkerWidth.js";
|
|
12
|
-
import { findWordBreaks, isBreakChar } from "./lineBreaks.js";
|
|
15
|
+
import { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation } from "./lineBreaks.js";
|
|
13
16
|
//#region src/layout-engine/measure/measureParagraph.ts
|
|
14
17
|
/**
|
|
15
18
|
* Paragraph measurement module
|
|
@@ -22,9 +25,11 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
22
25
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
26
|
const WIDTH_TOLERANCE = .5;
|
|
24
27
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
28
|
+
const JUSTIFY_SPACE_CONTRACTION_RATIO = .075;
|
|
29
|
+
const JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO = .195;
|
|
30
|
+
const JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO = .23;
|
|
25
31
|
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
26
32
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
27
|
-
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .02;
|
|
28
33
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
29
34
|
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
30
35
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
@@ -32,18 +37,25 @@ const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
|
32
37
|
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
33
38
|
* Returns the number of characters that fit (at least 1 if `forceMin` is true).
|
|
34
39
|
*/
|
|
35
|
-
function findMaxFittingLength(text, style, maxWidth, forceMin = false) {
|
|
36
|
-
|
|
37
|
-
let
|
|
40
|
+
function findMaxFittingLength(text, style, maxWidth, forceMin = false, policy) {
|
|
41
|
+
const boundaries = findGraphemeBreaks(text, policy);
|
|
42
|
+
let lo = 0;
|
|
43
|
+
let hi = boundaries.length - 1;
|
|
38
44
|
let best = 0;
|
|
39
45
|
while (lo <= hi) {
|
|
40
46
|
const mid = Math.floor((lo + hi) / 2);
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
const boundary = boundaries[mid];
|
|
48
|
+
if (measureTextWidth(text.slice(0, boundary), style) <= maxWidth) {
|
|
49
|
+
best = boundary;
|
|
43
50
|
lo = mid + 1;
|
|
44
51
|
} else hi = mid - 1;
|
|
45
52
|
}
|
|
46
|
-
return forceMin && best === 0 ?
|
|
53
|
+
return forceMin && best === 0 ? boundaries.at(0) ?? 0 : best;
|
|
54
|
+
}
|
|
55
|
+
function exceedsHyphenationZone(line, zoneTwips) {
|
|
56
|
+
if (line.width <= 0) return true;
|
|
57
|
+
const visibleWidth = Math.max(0, line.width - line.trailingWhitespaceWidth);
|
|
58
|
+
return Math.max(0, line.availableWidth - visibleWidth) > twipsToPx(zoneTwips) + WIDTH_TOLERANCE;
|
|
47
59
|
}
|
|
48
60
|
/**
|
|
49
61
|
* Extract FontStyle from a run that carries RunFormatting (text, tab, or
|
|
@@ -331,34 +343,63 @@ function uppercaseLetterRatio(text) {
|
|
|
331
343
|
}
|
|
332
344
|
return letters === 0 ? 0 : uppercase / letters;
|
|
333
345
|
}
|
|
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) {
|
|
346
|
+
function resolveJustifyFitStrategy(block, isFirstLine, profile) {
|
|
347
|
+
if (isShallowFullHangingListContinuation(block, isFirstLine)) return { type: "rounding" };
|
|
340
348
|
if (block.attrs?.listMarker !== void 0) {
|
|
341
349
|
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
342
|
-
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ?
|
|
350
|
+
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? {
|
|
351
|
+
type: "space",
|
|
352
|
+
ratio: JUSTIFY_LIST_MARKER_SPACE_CONTRACTION_RATIO
|
|
353
|
+
} : {
|
|
354
|
+
type: "width",
|
|
355
|
+
ratio: JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO
|
|
356
|
+
};
|
|
343
357
|
const left = block.attrs.indent?.left ?? 0;
|
|
344
|
-
if (hanging > 0 && left > hanging) return
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
if (hanging > 0 && left > hanging) return {
|
|
359
|
+
type: "width",
|
|
360
|
+
ratio: JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO
|
|
361
|
+
};
|
|
362
|
+
return {
|
|
363
|
+
type: "space",
|
|
364
|
+
ratio: JUSTIFY_LIST_CONTINUATION_SPACE_CONTRACTION_RATIO
|
|
365
|
+
};
|
|
350
366
|
}
|
|
351
367
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
368
|
+
if (isFirstLine && profile.hasTabRuns && (block.attrs?.indent?.hanging ?? 0) > 0) return {
|
|
369
|
+
type: "width",
|
|
370
|
+
ratio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
371
|
+
};
|
|
372
|
+
if (!isFirstLine && profile.hasTabRuns && !hasTabStops) return {
|
|
373
|
+
type: "width",
|
|
374
|
+
ratio: JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO
|
|
375
|
+
};
|
|
376
|
+
if (profile.hasTabRuns && (isFirstLine || hasTabStops)) return {
|
|
377
|
+
type: "width",
|
|
378
|
+
ratio: (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
379
|
+
};
|
|
380
|
+
if (profile.uppercaseRatio > ALL_CAPS_RATIO_THRESHOLD) return {
|
|
381
|
+
type: "width",
|
|
382
|
+
ratio: JUSTIFY_SHRINK_TOLERANCE_RATIO
|
|
383
|
+
};
|
|
384
|
+
return {
|
|
385
|
+
type: "space",
|
|
386
|
+
ratio: JUSTIFY_SPACE_CONTRACTION_RATIO
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function compressibleSpaceWidth(text, style) {
|
|
390
|
+
const count = countCompressibleSpaces(text);
|
|
391
|
+
return count === 0 ? 0 : count * measureTextWidth(" ", style);
|
|
392
|
+
}
|
|
393
|
+
function justifyFitTolerance(line, strategy, candidateSpaceWidth) {
|
|
394
|
+
if (strategy.type === "rounding") return WIDTH_TOLERANCE;
|
|
395
|
+
if (strategy.type === "width") return Math.max(WIDTH_TOLERANCE, line.availableWidth * strategy.ratio);
|
|
396
|
+
return Math.max(WIDTH_TOLERANCE, (line.regularSpaceWidth + candidateSpaceWidth) * strategy.ratio);
|
|
397
|
+
}
|
|
398
|
+
function isShallowFullHangingListContinuation(block, isFirstLine) {
|
|
399
|
+
if (isFirstLine || block.attrs?.listMarker === void 0) return false;
|
|
400
|
+
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
401
|
+
const left = block.attrs.indent?.left ?? 0;
|
|
402
|
+
return hanging > 0 && hanging < DEFAULT_LIST_HANGING_INDENT_PX && Math.abs(left - hanging) <= WIDTH_TOLERANCE;
|
|
362
403
|
}
|
|
363
404
|
function trimTrailingSpacesAndTabs(text) {
|
|
364
405
|
let end = text.length;
|
|
@@ -369,12 +410,20 @@ function trimTrailingSpacesAndTabs(text) {
|
|
|
369
410
|
}
|
|
370
411
|
return text.slice(0, end);
|
|
371
412
|
}
|
|
413
|
+
function trailingHangingPunctuationWidth(text, style, policy, enabled) {
|
|
414
|
+
if (!enabled || text.length === 0) return 0;
|
|
415
|
+
const lastStart = findGraphemeBreaks(text, policy).at(-2) ?? 0;
|
|
416
|
+
const lastGrapheme = text.slice(lastStart);
|
|
417
|
+
if (!isHangingPunctuation(lastGrapheme, policy)) return 0;
|
|
418
|
+
return measureTextWidth(lastGrapheme, style);
|
|
419
|
+
}
|
|
372
420
|
/**
|
|
373
421
|
* Width of the unbreakable text glued to the end of each run.
|
|
374
422
|
* A run boundary is not itself a wrap opportunity: adjacent note markers and
|
|
375
423
|
* format-only word splits must wrap as one cluster.
|
|
376
424
|
*/
|
|
377
|
-
function computeTrailingGlueWidths(
|
|
425
|
+
function computeTrailingGlueWidths(block) {
|
|
426
|
+
const runs = block.runs;
|
|
378
427
|
const widths = Array.from({ length: runs.length }, () => 0);
|
|
379
428
|
for (let index = runs.length - 1; index >= 0; index--) {
|
|
380
429
|
const nextRun = runs[index + 1];
|
|
@@ -384,19 +433,124 @@ function computeTrailingGlueWidths(runs) {
|
|
|
384
433
|
widths[index] = widths[index + 1] ?? 0;
|
|
385
434
|
continue;
|
|
386
435
|
}
|
|
387
|
-
if (isSpaceOrTab(text
|
|
436
|
+
if (isSpaceOrTab(text.at(0))) continue;
|
|
388
437
|
const style = runToFontStyle(nextRun);
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
438
|
+
const firstBreak = findWordBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
439
|
+
attrs: block.attrs,
|
|
440
|
+
run: nextRun
|
|
441
|
+
}).provider).at(0);
|
|
442
|
+
widths[index] = measureTextWidth(firstBreak === void 0 ? text : trimTrailingSpacesAndTabs(text.slice(0, firstBreak)), style) + (firstBreak === void 0 ? widths[index + 1] ?? 0 : 0);
|
|
443
|
+
}
|
|
444
|
+
return widths;
|
|
445
|
+
}
|
|
446
|
+
function computeProtectedCrossRunGlueWidths(block) {
|
|
447
|
+
const widths = Array.from({ length: block.runs.length }, () => 0);
|
|
448
|
+
for (let index = 0; index < block.runs.length; index++) {
|
|
449
|
+
const run = block.runs[index];
|
|
450
|
+
if (!run || !isTextRun(run)) continue;
|
|
451
|
+
const trailing = /(\S+)(\s*)$/u.exec(run.text ?? "");
|
|
452
|
+
const token = trailing?.[1];
|
|
453
|
+
const policy = resolveEffectiveLineBreakPolicy({
|
|
454
|
+
attrs: block.attrs,
|
|
455
|
+
run
|
|
456
|
+
}).provider;
|
|
457
|
+
if (!token || token.length !== 1 || !policy.locale?.toLocaleLowerCase().startsWith("cs")) continue;
|
|
458
|
+
let separator = trailing[2] ?? "";
|
|
459
|
+
let glueWidth = separator.length > 0 ? measureTextWidth(separator, runToFontStyle(run)) : 0;
|
|
460
|
+
let followingWord = "";
|
|
461
|
+
let unseparatedFollowingText = false;
|
|
462
|
+
for (let nextIndex = index + 1; nextIndex < block.runs.length; nextIndex++) {
|
|
463
|
+
const nextRun = block.runs[nextIndex];
|
|
464
|
+
if (!nextRun || !isTextRun(nextRun)) break;
|
|
465
|
+
const text = nextRun.text ?? "";
|
|
466
|
+
let consumed = "";
|
|
467
|
+
for (const char of text) {
|
|
468
|
+
const isWhitespace = /\s/u.test(char);
|
|
469
|
+
if (followingWord.length > 0 && isWhitespace) break;
|
|
470
|
+
if (separator.length === 0 && !isWhitespace) {
|
|
471
|
+
unseparatedFollowingText = true;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
consumed += char;
|
|
475
|
+
if (isWhitespace) separator += char;
|
|
476
|
+
else followingWord += char;
|
|
477
|
+
}
|
|
478
|
+
if (consumed.length > 0) glueWidth += measureTextWidth(consumed, runToFontStyle(nextRun));
|
|
479
|
+
if (unseparatedFollowingText) break;
|
|
480
|
+
if (followingWord.length > 0 && consumed.length < text.length) break;
|
|
393
481
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
482
|
+
if (unseparatedFollowingText || separator.length === 0 || followingWord.length === 0) continue;
|
|
483
|
+
const boundary = token.length + separator.length;
|
|
484
|
+
if (!findWordBreaks(token + separator + followingWord, policy).includes(boundary)) widths[index] = glueWidth;
|
|
397
485
|
}
|
|
398
486
|
return widths;
|
|
399
487
|
}
|
|
488
|
+
/** Collect one lexical word continued through adjacent formatting runs. */
|
|
489
|
+
function collectCrossRunWord({ block, startRunIndex, startChar }) {
|
|
490
|
+
const segments = [];
|
|
491
|
+
let text = "";
|
|
492
|
+
let width = 0;
|
|
493
|
+
for (let runIndex = startRunIndex; runIndex < block.runs.length; runIndex++) {
|
|
494
|
+
const run = block.runs[runIndex];
|
|
495
|
+
if (!run || !isTextRun(run)) break;
|
|
496
|
+
const start = runIndex === startRunIndex ? startChar : 0;
|
|
497
|
+
const remainder = run.text.slice(start);
|
|
498
|
+
if (remainder.length === 0) continue;
|
|
499
|
+
if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
|
|
500
|
+
const remainingBudget = 256 - text.length;
|
|
501
|
+
const boundedRemainder = remainder.slice(0, remainingBudget + 1);
|
|
502
|
+
const firstBreak = findWordBreaks(boundedRemainder, resolveEffectiveLineBreakPolicy({
|
|
503
|
+
attrs: block.attrs,
|
|
504
|
+
run
|
|
505
|
+
}).provider).at(0);
|
|
506
|
+
const segmentText = trimTrailingSpacesAndTabs(firstBreak === void 0 ? boundedRemainder : boundedRemainder.slice(0, firstBreak));
|
|
507
|
+
if (segmentText.length === 0) break;
|
|
508
|
+
if (segmentText.length > remainingBudget) return;
|
|
509
|
+
const style = runToFontStyle(run);
|
|
510
|
+
segments.push({
|
|
511
|
+
runIndex,
|
|
512
|
+
startChar: start,
|
|
513
|
+
text: segmentText,
|
|
514
|
+
run,
|
|
515
|
+
style
|
|
516
|
+
});
|
|
517
|
+
text += segmentText;
|
|
518
|
+
width += measureTextWidth(segmentText, style);
|
|
519
|
+
if (firstBreak !== void 0) break;
|
|
520
|
+
}
|
|
521
|
+
if (segments.length < 2) return;
|
|
522
|
+
const firstRun = segments[0]?.run;
|
|
523
|
+
if (!firstRun) return;
|
|
524
|
+
return {
|
|
525
|
+
text,
|
|
526
|
+
width,
|
|
527
|
+
breaks: findHyphenationBreaks(text, resolveEffectiveLineBreakPolicy({
|
|
528
|
+
attrs: block.attrs,
|
|
529
|
+
run: firstRun
|
|
530
|
+
}).provider),
|
|
531
|
+
segments
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function measureCrossRunPrefix(word, breakOffset) {
|
|
535
|
+
let width = 0;
|
|
536
|
+
let remaining = breakOffset;
|
|
537
|
+
const measuredSegments = [];
|
|
538
|
+
for (const segment of word.segments) {
|
|
539
|
+
const length = Math.min(remaining, segment.text.length);
|
|
540
|
+
if (length > 0) {
|
|
541
|
+
width += measureTextWidth(segment.text.slice(0, length), segment.style);
|
|
542
|
+
measuredSegments.push(segment);
|
|
543
|
+
}
|
|
544
|
+
if (remaining <= segment.text.length) return {
|
|
545
|
+
width,
|
|
546
|
+
endRunIndex: segment.runIndex,
|
|
547
|
+
endChar: segment.startChar + remaining,
|
|
548
|
+
hyphenStyle: segment.style,
|
|
549
|
+
segments: measuredSegments
|
|
550
|
+
};
|
|
551
|
+
remaining -= segment.text.length;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
400
554
|
/**
|
|
401
555
|
* Minimum horizontal room a line must offer before we treat it as usable for
|
|
402
556
|
* body text. Below this threshold the line is bumped past obstructing floats
|
|
@@ -446,6 +600,12 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
446
600
|
const attrs = block.attrs;
|
|
447
601
|
const spacing = attrs?.spacing;
|
|
448
602
|
const isJustifiedParagraph = attrs?.alignment === "justify";
|
|
603
|
+
const justificationProfile = {
|
|
604
|
+
hasTabRuns: isJustifiedParagraph && runs.some(isTabRun),
|
|
605
|
+
uppercaseRatio: isJustifiedParagraph ? uppercaseLetterRatio(runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) : 0
|
|
606
|
+
};
|
|
607
|
+
const firstLineJustifyFitStrategy = resolveJustifyFitStrategy(block, true, justificationProfile);
|
|
608
|
+
const continuationJustifyFitStrategy = resolveJustifyFitStrategy(block, false, justificationProfile);
|
|
449
609
|
const floatingZones = options?.floatingZones;
|
|
450
610
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
451
611
|
const indent = attrs?.indent;
|
|
@@ -476,6 +636,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
476
636
|
const firstLineFloatingMargins = getFloatingMargins(cumulativeHeight, estimatedFirstLineHeight, floatingZones, paragraphYOffset);
|
|
477
637
|
const firstLineWidth = Math.max(1, getFloatingAvailableWidth(firstLineFloatingMargins, baseFirstLineWidth));
|
|
478
638
|
const lines = [];
|
|
639
|
+
let consecutiveHyphenatedLines = 0;
|
|
479
640
|
if (runs.length === 0) {
|
|
480
641
|
if (attrs?.suppressEmptyParagraphHeight) {
|
|
481
642
|
lines.push({
|
|
@@ -534,7 +695,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
534
695
|
totalHeight
|
|
535
696
|
};
|
|
536
697
|
}
|
|
537
|
-
const trailingGlueWidths = computeTrailingGlueWidths(
|
|
698
|
+
const trailingGlueWidths = computeTrailingGlueWidths(block);
|
|
699
|
+
const protectedCrossRunGlueWidths = computeProtectedCrossRunGlueWidths(block);
|
|
538
700
|
let currentLine = {
|
|
539
701
|
fromRun: 0,
|
|
540
702
|
fromChar: 0,
|
|
@@ -542,11 +704,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
542
704
|
toChar: 0,
|
|
543
705
|
width: 0,
|
|
544
706
|
trailingWhitespaceWidth: 0,
|
|
545
|
-
|
|
546
|
-
nonBreakingSpaceCount: 0,
|
|
707
|
+
regularSpaceWidth: 0,
|
|
547
708
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
548
709
|
maxFontMetrics: null,
|
|
549
710
|
maxImageHeightPx: 0,
|
|
711
|
+
maxExactImageHeightPx: 0,
|
|
550
712
|
maxMathHeightPx: 0,
|
|
551
713
|
availableWidth: firstLineWidth,
|
|
552
714
|
leftOffset: firstLineFloatingMargins.leftMargin,
|
|
@@ -566,9 +728,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
566
728
|
if (inlineObjectHeight > finalTypography.lineHeight) {
|
|
567
729
|
const objectHeight = inlineObjectHeight;
|
|
568
730
|
const buffer = finalTypography.descent;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
finalTypography.
|
|
731
|
+
const soleRun = line.fromRun === line.toRun ? runs[line.fromRun] : void 0;
|
|
732
|
+
if (line.maxExactImageHeightPx >= objectHeight) {
|
|
733
|
+
finalTypography.lineHeight = objectHeight;
|
|
734
|
+
finalTypography.ascent = objectHeight;
|
|
735
|
+
finalTypography.descent = 0;
|
|
736
|
+
return finalTypography;
|
|
737
|
+
}
|
|
738
|
+
if (soleRun && isImageRun(soleRun)) {
|
|
739
|
+
finalTypography.lineHeight = objectHeight;
|
|
740
|
+
finalTypography.ascent = objectHeight;
|
|
741
|
+
finalTypography.descent = 0;
|
|
572
742
|
} else {
|
|
573
743
|
finalTypography.lineHeight = objectHeight + buffer;
|
|
574
744
|
finalTypography.ascent = objectHeight;
|
|
@@ -598,7 +768,8 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
598
768
|
toRun: currentLine.toRun,
|
|
599
769
|
toChar: currentLine.toChar,
|
|
600
770
|
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
601
|
-
...finalTypography
|
|
771
|
+
...finalTypography,
|
|
772
|
+
...currentLine.discretionaryHyphen ? { discretionaryHyphen: currentLine.discretionaryHyphen } : {}
|
|
602
773
|
};
|
|
603
774
|
if (currentLine.leftOffset > 0) line.leftOffset = currentLine.leftOffset;
|
|
604
775
|
if (currentLine.rightOffset > 0) line.rightOffset = currentLine.rightOffset;
|
|
@@ -607,6 +778,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
607
778
|
pendingFloatSkip = 0;
|
|
608
779
|
}
|
|
609
780
|
lines.push(line);
|
|
781
|
+
consecutiveHyphenatedLines = currentLine.discretionaryHyphen ? consecutiveHyphenatedLines + 1 : 0;
|
|
610
782
|
cumulativeHeight += finalTypography.lineHeight;
|
|
611
783
|
};
|
|
612
784
|
/**
|
|
@@ -625,11 +797,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
625
797
|
toChar: charIndex,
|
|
626
798
|
width: 0,
|
|
627
799
|
trailingWhitespaceWidth: 0,
|
|
628
|
-
|
|
629
|
-
nonBreakingSpaceCount: 0,
|
|
800
|
+
regularSpaceWidth: 0,
|
|
630
801
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
631
802
|
maxFontMetrics: null,
|
|
632
803
|
maxImageHeightPx: 0,
|
|
804
|
+
maxExactImageHeightPx: 0,
|
|
633
805
|
maxMathHeightPx: 0,
|
|
634
806
|
availableWidth: adjustedWidth,
|
|
635
807
|
leftOffset: floatingMargins.leftMargin,
|
|
@@ -651,7 +823,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
651
823
|
const metrics = getFontMetrics(style);
|
|
652
824
|
if (metrics.singleLineRatio > currentLine.maxFontMetrics.singleLineRatio) currentLine.maxFontMetrics = metrics;
|
|
653
825
|
};
|
|
826
|
+
let crossRunResume;
|
|
654
827
|
for (let runIndex = 0; runIndex < runs.length; runIndex++) {
|
|
828
|
+
if (crossRunResume && runIndex < crossRunResume.runIndex) continue;
|
|
655
829
|
const run = runs[runIndex];
|
|
656
830
|
if (isLineBreakRun(run)) {
|
|
657
831
|
currentLine.toRun = runIndex;
|
|
@@ -713,6 +887,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
713
887
|
if (currentLine.width > 0 && currentLine.width + imageWidth > currentLine.availableWidth + WIDTH_TOLERANCE) startNewLine(runIndex, 0);
|
|
714
888
|
const imageFootprintPx = imageHeight + (run.distTop ?? 0) + (run.distBottom ?? 0);
|
|
715
889
|
if (imageFootprintPx > currentLine.maxImageHeightPx) currentLine.maxImageHeightPx = imageFootprintPx;
|
|
890
|
+
if (run.exactLineHeight === true && imageFootprintPx > currentLine.maxExactImageHeightPx) currentLine.maxExactImageHeightPx = imageFootprintPx;
|
|
716
891
|
currentLine.width += imageWidth;
|
|
717
892
|
currentLine.trailingWhitespaceWidth = 0;
|
|
718
893
|
currentLine.toRun = runIndex;
|
|
@@ -724,11 +899,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
724
899
|
const style = buildRunFontStyle(run, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
|
|
725
900
|
updateMaxFont(style);
|
|
726
901
|
const fieldWidth = measureTextWidth(fallback, style);
|
|
727
|
-
|
|
902
|
+
const fieldSpaceWidth = compressibleSpaceWidth(fallback, style);
|
|
903
|
+
const fieldTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, lines.length === 0 ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, fieldSpaceWidth) : WIDTH_TOLERANCE;
|
|
904
|
+
if (currentLine.width > 0 && currentLine.width + fieldWidth > currentLine.availableWidth + fieldTolerance) {
|
|
728
905
|
startNewLine(runIndex, 0);
|
|
729
906
|
updateMaxFont(style);
|
|
730
907
|
}
|
|
731
908
|
currentLine.width += fieldWidth;
|
|
909
|
+
currentLine.regularSpaceWidth += fieldSpaceWidth;
|
|
732
910
|
currentLine.trailingWhitespaceWidth = 0;
|
|
733
911
|
currentLine.toRun = runIndex;
|
|
734
912
|
currentLine.toChar = 1;
|
|
@@ -759,6 +937,11 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
759
937
|
const textRun = run;
|
|
760
938
|
const text = textRun.text;
|
|
761
939
|
const style = runToFontStyle(textRun);
|
|
940
|
+
const effectiveLineBreakPolicy = resolveEffectiveLineBreakPolicy({
|
|
941
|
+
attrs: block.attrs,
|
|
942
|
+
run: textRun
|
|
943
|
+
});
|
|
944
|
+
const breakPolicy = effectiveLineBreakPolicy.provider;
|
|
762
945
|
const lineHeightStyle = cjkLineHeightStyle(textRun, style);
|
|
763
946
|
updateMaxFont(lineHeightStyle);
|
|
764
947
|
if (!text || text.length === 0) {
|
|
@@ -766,8 +949,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
766
949
|
currentLine.toChar = 0;
|
|
767
950
|
continue;
|
|
768
951
|
}
|
|
769
|
-
const wordBreaks = findWordBreaks(text);
|
|
770
|
-
let charIndex = 0;
|
|
952
|
+
const wordBreaks = findWordBreaks(text, breakPolicy);
|
|
953
|
+
let charIndex = crossRunResume?.runIndex === runIndex ? crossRunResume.charIndex : 0;
|
|
954
|
+
if (crossRunResume?.runIndex === runIndex) crossRunResume = void 0;
|
|
955
|
+
let activeHyphenationWord;
|
|
771
956
|
while (charIndex < text.length) {
|
|
772
957
|
let nextBreak = text.length;
|
|
773
958
|
for (const breakPoint of wordBreaks) if (breakPoint > charIndex) {
|
|
@@ -778,29 +963,102 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
778
963
|
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
779
964
|
const wordWidth = measureTextWidth(measuredWord, style);
|
|
780
965
|
const fullWordWidth = measureTextWidth(word, style);
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
|
|
966
|
+
const hangingPunctuationWidth = trailingHangingPunctuationWidth(measuredWord, style, breakPolicy, effectiveLineBreakPolicy.hangingPunctuation);
|
|
967
|
+
const isFirstLine = lines.length === 0;
|
|
968
|
+
const regularSpaceWidth = compressibleSpaceWidth(measuredWord, style);
|
|
969
|
+
const widthTolerance = isJustifiedParagraph ? justifyFitTolerance(currentLine, isFirstLine ? firstLineJustifyFitStrategy : continuationJustifyFitStrategy, regularSpaceWidth) : WIDTH_TOLERANCE;
|
|
970
|
+
const automaticHyphenation = effectiveLineBreakPolicy.automaticHyphenation;
|
|
971
|
+
const mayHyphenateLine = automaticHyphenation.type === "enabled" && (automaticHyphenation.consecutiveLineLimit === 0 || consecutiveHyphenatedLines < automaticHyphenation.consecutiveLineLimit) && exceedsHyphenationZone(currentLine, automaticHyphenation.hyphenationZoneTwips);
|
|
972
|
+
const isRunTail = nextBreak === text.length;
|
|
973
|
+
const crossRunWord = mayHyphenateLine && isRunTail && word.length > 0 && !isBreakChar(word.at(-1)) ? collectCrossRunWord({
|
|
974
|
+
block,
|
|
975
|
+
startRunIndex: runIndex,
|
|
976
|
+
startChar: charIndex
|
|
977
|
+
}) : void 0;
|
|
978
|
+
if (mayHyphenateLine && crossRunWord === void 0 && measuredWord.length > 0 && (activeHyphenationWord === void 0 || charIndex < activeHyphenationWord.start || charIndex >= activeHyphenationWord.end)) {
|
|
979
|
+
const fullWordEnd = charIndex + measuredWord.length;
|
|
980
|
+
const fullWordText = text.slice(charIndex, fullWordEnd);
|
|
981
|
+
activeHyphenationWord = {
|
|
982
|
+
start: charIndex,
|
|
983
|
+
end: fullWordEnd,
|
|
984
|
+
text: fullWordText,
|
|
985
|
+
breaks: findHyphenationBreaks(fullWordText, breakPolicy)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
const overflowsCurrentLine = wordWidth > 0 && currentLine.width + wordWidth > currentLine.availableWidth + widthTolerance;
|
|
989
|
+
if (mayHyphenateLine && overflowsCurrentLine && activeHyphenationWord) {
|
|
990
|
+
const consumed = charIndex - activeHyphenationWord.start;
|
|
991
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
992
|
+
const hyphenWidth = measureTextWidth("-", style);
|
|
993
|
+
let fittingBreak;
|
|
994
|
+
for (const breakOffset of activeHyphenationWord.breaks) {
|
|
995
|
+
if (breakOffset <= consumed || breakOffset >= activeHyphenationWord.text.length) continue;
|
|
996
|
+
if (measureTextWidth(activeHyphenationWord.text.slice(consumed, breakOffset), style) + hyphenWidth <= spaceLeft) fittingBreak = breakOffset;
|
|
997
|
+
}
|
|
998
|
+
if (fittingBreak !== void 0) {
|
|
999
|
+
const absoluteBreak = activeHyphenationWord.start + fittingBreak;
|
|
1000
|
+
const prefix = text.slice(charIndex, absoluteBreak);
|
|
1001
|
+
currentLine.width += measureTextWidth(prefix, style) + hyphenWidth;
|
|
1002
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
1003
|
+
currentLine.toRun = runIndex;
|
|
1004
|
+
currentLine.toChar = absoluteBreak;
|
|
1005
|
+
currentLine.discretionaryHyphen = { runIndex };
|
|
1006
|
+
startNewLine(runIndex, absoluteBreak);
|
|
1007
|
+
updateMaxFont(lineHeightStyle);
|
|
1008
|
+
charIndex = absoluteBreak;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (crossRunWord && currentLine.width + crossRunWord.width > currentLine.availableWidth + widthTolerance) {
|
|
1013
|
+
const spaceLeft = currentLine.availableWidth - currentLine.width + widthTolerance;
|
|
1014
|
+
let fittingPrefix;
|
|
1015
|
+
for (let breakIndex = crossRunWord.breaks.length - 1; breakIndex >= 0; breakIndex -= 1) {
|
|
1016
|
+
const breakOffset = crossRunWord.breaks[breakIndex];
|
|
1017
|
+
if (breakOffset === void 0) continue;
|
|
1018
|
+
if (breakOffset <= 0 || breakOffset >= crossRunWord.text.length) continue;
|
|
1019
|
+
const prefix = measureCrossRunPrefix(crossRunWord, breakOffset);
|
|
1020
|
+
if (prefix && prefix.width + measureTextWidth("-", prefix.hyphenStyle) <= spaceLeft) {
|
|
1021
|
+
fittingPrefix = prefix;
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (fittingPrefix) {
|
|
1026
|
+
for (const segment of fittingPrefix.segments) updateMaxFont(cjkLineHeightStyle(segment.run, segment.style));
|
|
1027
|
+
currentLine.width += fittingPrefix.width + measureTextWidth("-", fittingPrefix.hyphenStyle);
|
|
1028
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
1029
|
+
currentLine.toRun = fittingPrefix.endRunIndex;
|
|
1030
|
+
currentLine.toChar = fittingPrefix.endChar;
|
|
1031
|
+
currentLine.discretionaryHyphen = { runIndex: fittingPrefix.endRunIndex };
|
|
1032
|
+
startNewLine(fittingPrefix.endRunIndex, fittingPrefix.endChar);
|
|
1033
|
+
if (fittingPrefix.endRunIndex === runIndex) {
|
|
1034
|
+
updateMaxFont(lineHeightStyle);
|
|
1035
|
+
charIndex = fittingPrefix.endChar;
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
crossRunResume = {
|
|
1039
|
+
runIndex: fittingPrefix.endRunIndex,
|
|
1040
|
+
charIndex: fittingPrefix.endChar
|
|
1041
|
+
};
|
|
1042
|
+
charIndex = text.length;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (wordWidth - hangingPunctuationWidth > currentLine.availableWidth + widthTolerance) {
|
|
785
1047
|
let chunkStart = 0;
|
|
786
1048
|
while (chunkStart < measuredWord.length) {
|
|
787
1049
|
const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
|
|
788
|
-
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft);
|
|
1050
|
+
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft, currentLine.width === 0, breakPolicy);
|
|
789
1051
|
if (bestEnd === 0) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
bestEnd = 1;
|
|
1052
|
+
startNewLine(runIndex, charIndex + chunkStart);
|
|
1053
|
+
updateMaxFont(lineHeightStyle);
|
|
1054
|
+
continue;
|
|
796
1055
|
}
|
|
797
1056
|
const chunkEnd = chunkStart + bestEnd;
|
|
798
1057
|
const chunk = measuredWord.slice(chunkStart, chunkEnd);
|
|
799
1058
|
const chunkWidth = measureTextWidth(chunk, style);
|
|
800
1059
|
currentLine.width += chunkWidth;
|
|
801
1060
|
currentLine.trailingWhitespaceWidth = chunkWidth - measureTextWidth(trimTrailingSpacesAndTabs(chunk), style);
|
|
802
|
-
currentLine.
|
|
803
|
-
currentLine.nonBreakingSpaceCount += chunk.split("\xA0").length - 1;
|
|
1061
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(chunk, style);
|
|
804
1062
|
currentLine.toRun = runIndex;
|
|
805
1063
|
currentLine.toChar = charIndex + chunkEnd;
|
|
806
1064
|
chunkStart = chunkEnd;
|
|
@@ -812,23 +1070,24 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
812
1070
|
const trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
813
1071
|
currentLine.width += trailingWhitespaceWidth;
|
|
814
1072
|
currentLine.trailingWhitespaceWidth = trailingWhitespaceWidth;
|
|
815
|
-
currentLine.
|
|
816
|
-
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
1073
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(word, style);
|
|
817
1074
|
currentLine.toChar = nextBreak;
|
|
818
1075
|
charIndex = nextBreak;
|
|
819
1076
|
continue;
|
|
820
1077
|
}
|
|
821
|
-
const
|
|
1078
|
+
const wordTail = word.at(-1);
|
|
1079
|
+
const protectedGlueWidth = protectedCrossRunGlueWidths[runIndex] ?? 0;
|
|
1080
|
+
const rawGlueWidth = isRunTail && wordTail !== void 0 && (!isBreakChar(wordTail) || protectedGlueWidth > 0) ? Math.max(trailingGlueWidths[runIndex] ?? 0, protectedGlueWidth) : 0;
|
|
822
1081
|
const glueWidth = rawGlueWidth > 0 && wordWidth + rawGlueWidth <= getPostWrapAvailableWidth() + widthTolerance ? rawGlueWidth : 0;
|
|
823
|
-
|
|
1082
|
+
const hangingAllowance = glueWidth === 0 ? hangingPunctuationWidth : 0;
|
|
1083
|
+
if (wordWidth > 0 && currentLine.width > 0 && currentLine.width + wordWidth + glueWidth > currentLine.availableWidth + widthTolerance + hangingAllowance) {
|
|
824
1084
|
startNewLine(runIndex, charIndex);
|
|
825
1085
|
updateMaxFont(lineHeightStyle);
|
|
826
1086
|
}
|
|
827
1087
|
currentLine.width += fullWordWidth;
|
|
828
1088
|
const wordTrailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
829
1089
|
currentLine.trailingWhitespaceWidth = wordWidth === 0 ? currentLine.trailingWhitespaceWidth + wordTrailingWhitespaceWidth : wordTrailingWhitespaceWidth;
|
|
830
|
-
currentLine.
|
|
831
|
-
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
1090
|
+
currentLine.regularSpaceWidth += compressibleSpaceWidth(word, style);
|
|
832
1091
|
currentLine.toRun = runIndex;
|
|
833
1092
|
currentLine.toChar = nextBreak;
|
|
834
1093
|
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
|