@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.
Files changed (168) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/headless.d.ts +73 -6
  4. package/dist/ai-edits/headless.js +299 -60
  5. package/dist/ai-edits/index.d.ts +3 -3
  6. package/dist/ai-edits/index.js +2 -1
  7. package/dist/ai-edits/snapshot.js +40 -3
  8. package/dist/ai-edits/types.d.ts +2 -1
  9. package/dist/compat/eigenpal.d.ts +6 -4
  10. package/dist/compat/eigenpal.js +5 -3
  11. package/dist/controller/latestRequestGate.d.ts +13 -0
  12. package/dist/controller/latestRequestGate.js +16 -0
  13. package/dist/controller/layoutPipeline.js +13 -1
  14. package/dist/document-operations.d.ts +20 -4
  15. package/dist/document-operations.js +36 -15
  16. package/dist/document-stories.d.ts +10 -0
  17. package/dist/document-stories.js +27 -0
  18. package/dist/docx/blockContentParser.js +2 -2
  19. package/dist/docx/commentParser.js +1 -1
  20. package/dist/docx/compatibility.d.ts +3 -7
  21. package/dist/docx/compatibility.js +0 -11
  22. package/dist/docx/corePropertiesParser.d.ts +8 -0
  23. package/dist/docx/corePropertiesParser.js +53 -0
  24. package/dist/docx/encryption/agileDecryption.js +1 -1
  25. package/dist/docx/encryption/encryptionInfo.js +1 -1
  26. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  27. package/dist/docx/fontTableParser.d.ts +6 -0
  28. package/dist/docx/fontTableParser.js +72 -0
  29. package/dist/docx/groupDrawingParser.js +7 -2
  30. package/dist/docx/headerFooterParser.js +1 -1
  31. package/dist/docx/index.d.ts +2 -1
  32. package/dist/docx/index.js +2 -1
  33. package/dist/docx/metadataPrivacy.d.ts +40 -0
  34. package/dist/docx/metadataPrivacy.js +131 -0
  35. package/dist/docx/normalizeBaseDirection.js +1 -1
  36. package/dist/docx/numberingParser.js +1 -0
  37. package/dist/docx/paragraphParser.js +12 -1
  38. package/dist/docx/parser.d.ts +1 -1
  39. package/dist/docx/parser.js +16 -10
  40. package/dist/docx/rezip.d.ts +5 -4
  41. package/dist/docx/rezip.js +76 -13
  42. package/dist/docx/runConsolidator.js +4 -0
  43. package/dist/docx/runParser.js +19 -1
  44. package/dist/docx/selectiveSave.js +3 -3
  45. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  46. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  47. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  48. package/dist/docx/serializer/paragraphSerializer.js +4 -0
  49. package/dist/docx/serializer/runSerializer.js +7 -0
  50. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  51. package/dist/docx/serializer/settingsSerializer.js +16 -0
  52. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  53. package/dist/docx/serializer/stylesSerializer.js +56 -0
  54. package/dist/docx/serializer/tableSerializer.js +25 -26
  55. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  56. package/dist/docx/serializer/themeSerializer.js +36 -0
  57. package/dist/docx/settingsParser.js +46 -0
  58. package/dist/docx/styleParser.js +26 -1
  59. package/dist/docx/tableParser.js +11 -1
  60. package/dist/docx/vmlImageParser.js +1 -0
  61. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  62. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  63. package/dist/index.d.ts +6 -4
  64. package/dist/index.js +5 -3
  65. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  66. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  67. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  68. package/dist/layout-bridge/convert/headerFooterLayout.js +6 -1
  69. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  70. package/dist/layout-bridge/convert/toFlowBlocks.js +52 -14
  71. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  72. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  73. package/dist/layout-engine/index.js +36 -28
  74. package/dist/layout-engine/measure/cache.js +6 -3
  75. package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
  76. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
  77. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  78. package/dist/layout-engine/measure/index.d.ts +2 -1
  79. package/dist/layout-engine/measure/index.js +2 -1
  80. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  81. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  82. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  83. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  84. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  85. package/dist/layout-engine/measure/listMarkerWidth.js +12 -6
  86. package/dist/layout-engine/measure/measureBlocks.d.ts +1 -2
  87. package/dist/layout-engine/measure/measureBlocks.js +6 -36
  88. package/dist/layout-engine/measure/measureContainer.js +32 -9
  89. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  90. package/dist/layout-engine/measure/measureParagraph.js +332 -73
  91. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  92. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  93. package/dist/layout-engine/measure/measureWorker.js +8 -19
  94. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  95. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  96. package/dist/layout-engine/measure/tableCellFloating.js +51 -37
  97. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  98. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  99. package/dist/layout-engine/measure/tableCellGrid.js +1 -1
  100. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  101. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  102. package/dist/layout-engine/renderedBreakReconciliation.d.ts +6 -1
  103. package/dist/layout-engine/renderedBreakReconciliation.js +34 -11
  104. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  105. package/dist/layout-engine/tableRowBreak.js +62 -29
  106. package/dist/layout-engine/types.d.ts +43 -8
  107. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  108. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  109. package/dist/layout-painter/borderStroke.d.ts +23 -0
  110. package/dist/layout-painter/borderStroke.js +39 -0
  111. package/dist/layout-painter/renderImage.d.ts +5 -4
  112. package/dist/layout-painter/renderImage.js +18 -4
  113. package/dist/layout-painter/renderPage.d.ts +2 -27
  114. package/dist/layout-painter/renderPage.js +13 -99
  115. package/dist/layout-painter/renderParagraph.js +41 -23
  116. package/dist/layout-painter/renderTable.d.ts +2 -0
  117. package/dist/layout-painter/renderTable.js +223 -43
  118. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  119. package/dist/managers/DocumentLoaderManager.js +2 -2
  120. package/dist/prosemirror/attrs/index.d.ts +4 -2
  121. package/dist/prosemirror/attrs/index.js +22 -2
  122. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  123. package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
  124. package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
  125. package/dist/prosemirror/conversion/fromProseDoc.js +34 -18
  126. package/dist/prosemirror/conversion/toProseDoc.js +81 -77
  127. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  128. package/dist/prosemirror/extensions/core/ParagraphExtension.js +7 -2
  129. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  130. package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
  131. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  132. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  133. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  134. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  135. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  136. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  137. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  138. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  139. package/dist/prosemirror/extensions/nodes/ImageExtension.js +3 -1
  140. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  141. package/dist/prosemirror/schema/index.d.ts +2 -2
  142. package/dist/prosemirror/schema/marks.d.ts +6 -1
  143. package/dist/prosemirror/schema/nodes.d.ts +15 -12
  144. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  145. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  146. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  147. package/dist/prosemirror/validation.js +4 -1
  148. package/dist/redline.d.ts +26 -11
  149. package/dist/redline.js +107 -64
  150. package/dist/server.d.ts +9 -5
  151. package/dist/server.js +8 -4
  152. package/dist/style-sets/extract.d.ts +35 -0
  153. package/dist/style-sets/extract.js +123 -0
  154. package/dist/style-sets/stellaStyle.d.ts +13 -0
  155. package/dist/style-sets/stellaStyle.js +516 -0
  156. package/dist/style-sets/types.d.ts +31 -0
  157. package/dist/style-sets/types.js +5 -0
  158. package/dist/utils/createDocument.d.ts +12 -2
  159. package/dist/utils/createDocument.js +42 -31
  160. package/dist/utils/fontResolver.js +6 -6
  161. package/dist/utils/formatToStyle.js +1 -1
  162. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  163. package/dist/utils/paragraphFormattingMerge.js +14 -4
  164. package/dist/version-comparison.d.ts +65 -11
  165. package/dist/version-comparison.js +187 -29
  166. package/package.json +5 -1
  167. package/dist/docx/capabilities.d.ts +0 -41
  168. package/dist/docx/capabilities.js +0 -322
@@ -1,3 +1,5 @@
1
+ import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
2
+ import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
1
3
  //#region src/layout-engine/measure/cache.ts
2
4
  /**
3
5
  * Current max size for text width cache
@@ -152,10 +154,10 @@ const paragraphMeasureCache = /* @__PURE__ */ new Map();
152
154
  * Used as cache key to identify identical content
153
155
  */
154
156
  function hashParagraphBlock(block) {
155
- const parts = [];
156
- for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.fontSize}|${run.bold}|${run.italic}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}`);
157
+ const parts = [`lbp:${getLineBreakProviderGeneration()}`];
158
+ for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.fontSize}|${run.bold}|${run.italic}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}|${run.language?.val}|${run.language?.eastAsia}|${run.language?.bidi}`);
157
159
  else if (run.kind === "tab") parts.push(`tab:${run.width}`);
158
- else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}`);
160
+ else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}:${run.exactLineHeight === true ? "exact" : "text"}`);
159
161
  else if (run.kind === "lineBreak") parts.push("br");
160
162
  const attrs = block.attrs;
161
163
  if (attrs) {
@@ -167,6 +169,7 @@ function hashParagraphBlock(block) {
167
169
  if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
168
170
  if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
169
171
  if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
172
+ parts.push(...lineBreakPolicyCacheParts(attrs));
170
173
  const borders = attrs.borders;
171
174
  if (borders) {
172
175
  const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
@@ -0,0 +1,29 @@
1
+ import { ParagraphAttrs, TextRun } from "../types.js";
2
+ import { LineBreakPolicy } from "./lineBreakProvider.js";
3
+
4
+ //#region src/layout-engine/measure/effectiveLineBreakPolicy.d.ts
5
+ type EffectiveAutomaticHyphenation = Readonly<{
6
+ type: "disabled";
7
+ }> | Readonly<{
8
+ type: "enabled";
9
+ consecutiveLineLimit: number;
10
+ hyphenationZoneTwips: number;
11
+ }>;
12
+ type EffectiveLineBreakPolicy = Readonly<{
13
+ /** Exact provider inputs; omitted OOXML values stay omitted for custom providers. */provider: Readonly<LineBreakPolicy>; /** `w:overflowPunct` defaults to enabled when omitted. */
14
+ hangingPunctuation: boolean; /** Effective document and paragraph automatic-hyphenation state. */
15
+ automaticHyphenation: EffectiveAutomaticHyphenation;
16
+ }>;
17
+ type ResolveEffectiveLineBreakPolicyOptions = {
18
+ attrs: ParagraphAttrs | undefined;
19
+ run: TextRun;
20
+ };
21
+ /** Resolve all authored line-breaking inputs used while measuring one text run. */
22
+ declare const resolveEffectiveLineBreakPolicy: ({
23
+ attrs,
24
+ run
25
+ }: ResolveEffectiveLineBreakPolicyOptions) => EffectiveLineBreakPolicy;
26
+ /** Cache fragments for every paragraph attribute consumed by the resolver. */
27
+ declare const lineBreakPolicyCacheParts: (attrs: ParagraphAttrs) => readonly string[];
28
+ //#endregion
29
+ export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
@@ -0,0 +1,60 @@
1
+ import { hasCjk } from "../../utils/scriptSegments.js";
2
+ //#region src/layout-engine/measure/effectiveLineBreakPolicy.ts
3
+ const DEFAULT_HYPHENATION_ZONE_TWIPS = 360;
4
+ /** Resolve all authored line-breaking inputs used while measuring one text run. */
5
+ const resolveEffectiveLineBreakPolicy = ({ attrs, run }) => {
6
+ const locale = resolveRunLocale(run);
7
+ const rules = attrs?.lineBreakRules;
8
+ const before = rules?.noLineBreaksBefore;
9
+ const after = rules?.noLineBreaksAfter;
10
+ const automaticHyphenation = attrs?.automaticHyphenation;
11
+ return {
12
+ provider: {
13
+ ...locale ? { locale } : {},
14
+ ...attrs?.kinsoku !== void 0 ? { kinsoku: attrs.kinsoku } : {},
15
+ ...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: before.characters } : {},
16
+ ...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: after.characters } : {},
17
+ ...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
18
+ ...automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: automaticHyphenation.doNotHyphenateCaps } : {},
19
+ ...run.allCaps === true ? { renderedAllCaps: true } : {}
20
+ },
21
+ hangingPunctuation: attrs?.overflowPunctuation !== false,
22
+ automaticHyphenation: resolveAutomaticHyphenation(attrs)
23
+ };
24
+ };
25
+ /** Cache fragments for every paragraph attribute consumed by the resolver. */
26
+ const lineBreakPolicyCacheParts = (attrs) => {
27
+ const parts = [];
28
+ if (attrs.kinsoku !== void 0) parts.push(`kinsoku:${attrs.kinsoku}`);
29
+ if (attrs.overflowPunctuation !== void 0) parts.push(`overflow-punct:${attrs.overflowPunctuation}`);
30
+ if (attrs.suppressAutoHyphens !== void 0) parts.push(`suppress-auto-hyphens:${attrs.suppressAutoHyphens}`);
31
+ const automaticHyphenation = attrs.automaticHyphenation;
32
+ if (automaticHyphenation) parts.push(`auto-hyphens:${automaticHyphenation.doNotHyphenateCaps}|${automaticHyphenation.consecutiveLineLimit}|${automaticHyphenation.hyphenationZoneTwips}`);
33
+ const rules = attrs.lineBreakRules;
34
+ if (rules) parts.push(`line-break-rules:${rules.noLineBreaksBefore?.language}|${rules.noLineBreaksBefore?.characters}|${rules.noLineBreaksAfter?.language}|${rules.noLineBreaksAfter?.characters}|${rules.useLegacyEthiopicAmharicRules}`);
35
+ return parts;
36
+ };
37
+ const resolveAutomaticHyphenation = (attrs) => {
38
+ const automaticHyphenation = attrs?.automaticHyphenation;
39
+ if (automaticHyphenation?.enabled !== true || attrs?.suppressAutoHyphens === true) return { type: "disabled" };
40
+ return {
41
+ type: "enabled",
42
+ consecutiveLineLimit: automaticHyphenation.consecutiveLineLimit ?? 0,
43
+ hyphenationZoneTwips: automaticHyphenation.hyphenationZoneTwips ?? DEFAULT_HYPHENATION_ZONE_TWIPS
44
+ };
45
+ };
46
+ const resolveRunLocale = (run) => {
47
+ const language = run.language;
48
+ if (hasCjk(run.text)) return language?.eastAsia ?? language?.val;
49
+ if (run.rtl) return language?.bidi ?? language?.val;
50
+ return language?.val;
51
+ };
52
+ const languageMatches = (ruleLanguage, locale) => {
53
+ if (!ruleLanguage) return true;
54
+ if (!locale) return false;
55
+ const rule = ruleLanguage.toLowerCase();
56
+ const active = locale.toLowerCase();
57
+ return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
58
+ };
59
+ //#endregion
60
+ export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
@@ -44,6 +44,7 @@ function isFontFingerprintMatch(context, expectedWidth) {
44
44
  function measureEntry(entry) {
45
45
  const context = getCtx();
46
46
  context.font = entry.font;
47
+ context.fontKerning = entry.fontKerning;
47
48
  if (!isFontFingerprintMatch(context, entry.fontFingerprintWidth)) return null;
48
49
  let width = context.measureText(entry.text).width;
49
50
  const codePoints = countCodePoints(entry.text);
@@ -1,9 +1,10 @@
1
1
  import { FontMetrics, FontStyle, RunMeasurement, TextMeasurement } from "./measureTypes.js";
2
2
  import { MeasureProvider, getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider } from "./measureProvider.js";
3
3
  import { buildFontString, findCharacterAtX, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx } from "./measureHelpers.js";
4
+ import { LineBreakPolicy, LineBreakProvider, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider } from "./lineBreakProvider.js";
4
5
  import { FloatingExclusionRect, FloatingImageZone, FloatingLineMargins, FloatingLineSegmentZone, WrapTextDirection, getFloatingAvailableWidth, getFloatingMargins, rectsToFloatingZones } from "./floatingZones.js";
5
6
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
6
7
  import { MeasureParagraphOptions, getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
7
8
  import { clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFontCacheSize, getParagraphCacheSize, getTextCacheSize, getTotalCacheSize, hashParagraphBlock, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFontCacheSize, setParagraphCacheSize, setTextCacheSize } from "./cache.js";
8
9
  import { FolioMeasurementFeatureFlags, isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
9
- export { type FloatingExclusionRect, type FloatingImageZone, type FloatingLineMargins, type FloatingLineSegmentZone, type FolioMeasurementFeatureFlags, type FontMetrics, type FontStyle, type MeasureParagraphOptions, type MeasureProvider, type RunMeasurement, type TextMeasurement, type WrapTextDirection, buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
10
+ export { type FloatingExclusionRect, type FloatingImageZone, type FloatingLineMargins, type FloatingLineSegmentZone, type FolioMeasurementFeatureFlags, type FontMetrics, type FontStyle, type LineBreakPolicy, type LineBreakProvider, type MeasureParagraphOptions, type MeasureProvider, type RunMeasurement, type TextMeasurement, type WrapTextDirection, buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, defaultLineBreakProvider, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getLineBreakProvider, getLineBreakProviderGeneration, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetLineBreakProvider, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
@@ -1,8 +1,9 @@
1
1
  import { getFontMetrics, getMeasureProvider, measureRun, measureText, measureTextWidth, resetMeasureProvider, setMeasureProvider } from "./measureProvider.js";
2
2
  import { buildFontString, findCharacterAtX, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx } from "./measureHelpers.js";
3
+ import { defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider } from "./lineBreakProvider.js";
3
4
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
4
5
  import { getFloatingAvailableWidth, getFloatingMargins, rectsToFloatingZones } from "./floatingZones.js";
5
6
  import { getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
6
7
  import { clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFontCacheSize, getParagraphCacheSize, getTextCacheSize, getTotalCacheSize, hashParagraphBlock, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFontCacheSize, setParagraphCacheSize, setTextCacheSize } from "./cache.js";
7
8
  import { isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
8
- export { buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
9
+ export { buildFontString, clampFloatingWrapMargins, clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, defaultLineBreakProvider, findCharacterAtX, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFloatingAvailableWidth, getFloatingMargins, getFontCacheSize, getFontMetrics, getLineBreakProvider, getLineBreakProviderGeneration, getMeasureProvider, getParagraphCacheSize, getRunCharWidths, getTextCacheSize, getTotalCacheSize, getXForCharacter, halfPtToPx, hashParagraphBlock, isWorkerFontMetricsEnabled, measureParagraph, measureParagraphs, measureRun, measureText, measureTextWidth, ptToPx, pxToHalfPt, pxToPt, pxToTwips, rectsToFloatingZones, resetLineBreakProvider, resetMeasureProvider, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
@@ -0,0 +1,33 @@
1
+ //#region src/layout-engine/measure/lineBreakProvider.d.ts
2
+ /**
3
+ * Replaceable line-break policy for the layout engine.
4
+ *
5
+ * Providers return UTF-16 offsets so their output composes directly with the
6
+ * run and ProseMirror positions used by the rest of the layout pipeline. The
7
+ * default provider uses the platform's Unicode segmenter, then applies the
8
+ * OOXML line-edge restrictions that are available in the flow model.
9
+ */
10
+ type LineBreakPolicy = {
11
+ /** BCP-47 language tag resolved from `w:lang` for this run. */locale?: string; /** Apply East Asian first/last-character restrictions (`w:kinsoku`). */
12
+ kinsoku?: boolean; /** Document replacement list: characters that may not begin a line. */
13
+ noLineBreaksBefore?: string; /** Document replacement list: characters that may not end a line. */
14
+ noLineBreaksAfter?: string; /** Use the legacy Ethiopic/Amharic compatibility behavior. */
15
+ useLegacyEthiopicAmharicRules?: boolean; /** Keep words made entirely of capital letters unhyphenated. */
16
+ doNotHyphenateCaps?: boolean; /** The run renders with the DOCX all-caps transform. */
17
+ renderedAllCaps?: boolean;
18
+ };
19
+ type LineBreakProvider = {
20
+ /** Legal soft-wrap offsets, in ascending UTF-16 order. */findBreaks: (text: string, policy?: LineBreakPolicy) => number[]; /** Grapheme-safe emergency-wrap offsets, in ascending UTF-16 order. */
21
+ findGraphemeBreaks: (text: string, policy?: LineBreakPolicy) => number[]; /** Dictionary-based discretionary hyphen offsets, in ascending UTF-16 order. */
22
+ findHyphenationBreaks?: (text: string, policy?: LineBreakPolicy) => number[]; /** Whether a trailing grapheme may overhang the line edge. */
23
+ isHangingPunctuation?: (text: string, policy?: LineBreakPolicy) => boolean;
24
+ };
25
+ declare const MAX_HYPHENATION_WORD_LENGTH = 256;
26
+ declare const defaultLineBreakProvider: LineBreakProvider;
27
+ declare const getLineBreakProvider: () => LineBreakProvider;
28
+ /** Changes whenever the active provider changes, so layout caches cannot go stale. */
29
+ declare const getLineBreakProviderGeneration: () => number;
30
+ declare const setLineBreakProvider: (provider: LineBreakProvider) => void;
31
+ declare const resetLineBreakProvider: () => void;
32
+ //#endregion
33
+ export { LineBreakPolicy, LineBreakProvider, MAX_HYPHENATION_WORD_LENGTH, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider };
@@ -0,0 +1,252 @@
1
+ import czechHyphenation from "hyphen/cs/index.js";
2
+ import britishEnglishHyphenation from "hyphen/en-gb/index.js";
3
+ import americanEnglishHyphenation from "hyphen/en-us/index.js";
4
+ import slovakHyphenation from "hyphen/sk/index.js";
5
+ //#region src/layout-engine/measure/lineBreakProvider.ts
6
+ /**
7
+ * Replaceable line-break policy for the layout engine.
8
+ *
9
+ * Providers return UTF-16 offsets so their output composes directly with the
10
+ * run and ProseMirror positions used by the rest of the layout pipeline. The
11
+ * default provider uses the platform's Unicode segmenter, then applies the
12
+ * OOXML line-edge restrictions that are available in the flow model.
13
+ */
14
+ const { hyphenateSync: hyphenateCzech } = czechHyphenation;
15
+ const { hyphenateSync: hyphenateBritishEnglish } = britishEnglishHyphenation;
16
+ const { hyphenateSync: hyphenateAmericanEnglish } = americanEnglishHyphenation;
17
+ const { hyphenateSync: hyphenateSlovak } = slovakHyphenation;
18
+ const SEGMENTER_CACHE_LIMIT = 16;
19
+ const DEFAULT_SEGMENTER_KEY = "";
20
+ const SOFT_HYPHEN = "­";
21
+ const MAX_HYPHENATION_WORD_LENGTH = 256;
22
+ const wordSegmenters = /* @__PURE__ */ new Map();
23
+ const graphemeSegmenters = /* @__PURE__ */ new Map();
24
+ const segmenterLocale = (locale) => {
25
+ const language = locale?.trim().replaceAll("_", "-").split("-").at(0);
26
+ return language && /^[a-z]{2,3}$/iu.test(language) ? language.toLowerCase() : void 0;
27
+ };
28
+ const DICTIONARY_BREAK_SCRIPT = /[\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
29
+ const CJK_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
30
+ const BREAK_AFTER_CHARACTER = /* @__PURE__ */ new Set([
31
+ "-",
32
+ "֊",
33
+ "־",
34
+ "᐀",
35
+ "᠆",
36
+ "‐",
37
+ "⸗",
38
+ "⹀",
39
+ "゠",
40
+ "﹣",
41
+ "-",
42
+ "​",
43
+ "­"
44
+ ]);
45
+ const CZECH_ONE_LETTER_PREPOSITIONS = /* @__PURE__ */ new Set([
46
+ "k",
47
+ "o",
48
+ "s",
49
+ "u",
50
+ "v",
51
+ "z"
52
+ ]);
53
+ const DEFAULT_PROHIBITED_LINE_START = /* @__PURE__ */ new Set([
54
+ "!",
55
+ "%",
56
+ ")",
57
+ ",",
58
+ ".",
59
+ ":",
60
+ ";",
61
+ "?",
62
+ "]",
63
+ "}",
64
+ "、",
65
+ "。",
66
+ "〉",
67
+ "》",
68
+ "」",
69
+ "』",
70
+ "】",
71
+ "〕",
72
+ ")",
73
+ "]",
74
+ "}",
75
+ ",",
76
+ ".",
77
+ ":",
78
+ ";",
79
+ "!",
80
+ "?",
81
+ "…",
82
+ "’",
83
+ "”"
84
+ ]);
85
+ const DEFAULT_PROHIBITED_LINE_END = /* @__PURE__ */ new Set([
86
+ "$",
87
+ "(",
88
+ "[",
89
+ "{",
90
+ "£",
91
+ "¥",
92
+ "〈",
93
+ "《",
94
+ "「",
95
+ "『",
96
+ "【",
97
+ "〔",
98
+ "(",
99
+ "[",
100
+ "{",
101
+ "‘",
102
+ "“"
103
+ ]);
104
+ const getSegmenter = (cache, granularity, locale) => {
105
+ const normalizedLocale = segmenterLocale(locale);
106
+ const key = normalizedLocale ?? DEFAULT_SEGMENTER_KEY;
107
+ const cached = cache.get(key);
108
+ if (cached) return cached;
109
+ const segmenter = new Intl.Segmenter(normalizedLocale, { granularity });
110
+ if (cache.size >= SEGMENTER_CACHE_LIMIT) {
111
+ const oldestKey = cache.keys().next().value;
112
+ if (typeof oldestKey === "string") cache.delete(oldestKey);
113
+ }
114
+ cache.set(key, segmenter);
115
+ return segmenter;
116
+ };
117
+ const firstCodePoint = (text, index) => {
118
+ if (index >= text.length) return;
119
+ return String.fromCodePoint(text.codePointAt(index) ?? 0);
120
+ };
121
+ const previousCodePoint = (text, index) => {
122
+ if (index <= 0) return;
123
+ const trailing = text.charCodeAt(index - 1);
124
+ if (trailing >= 56320 && trailing <= 57343 && index >= 2) return text.slice(index - 2, index);
125
+ return text[index - 1];
126
+ };
127
+ const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.includes(character) : DEFAULT_PROHIBITED_LINE_START.has(character));
128
+ const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.includes(character) : DEFAULT_PROHIBITED_LINE_END.has(character));
129
+ const isCjkLineBreakParticipant = (character, policy) => character !== void 0 && (CJK_SCRIPT.test(character) || DEFAULT_PROHIBITED_LINE_START.has(character) || DEFAULT_PROHIBITED_LINE_END.has(character) || policy?.noLineBreaksBefore?.includes(character) === true || policy?.noLineBreaksAfter?.includes(character) === true);
130
+ const isLegacyEthiopicBreakCharacter = (character, policy) => {
131
+ if (!policy?.useLegacyEthiopicAmharicRules || character === void 0) return false;
132
+ const codePoint = character.codePointAt(0);
133
+ return codePoint !== void 0 && codePoint >= 4961 && codePoint <= 4968;
134
+ };
135
+ const czechProtectedBreaks = (text, policy) => {
136
+ const breaks = /* @__PURE__ */ new Set();
137
+ if (segmenterLocale(policy?.locale) !== "cs") return breaks;
138
+ let tokenStart = 0;
139
+ let index = 0;
140
+ while (index < text.length) {
141
+ const character = firstCodePoint(text, index);
142
+ if (character === void 0) break;
143
+ if (!/\s/u.test(character)) {
144
+ index += character.length;
145
+ continue;
146
+ }
147
+ const token = text.slice(tokenStart, index);
148
+ const protectsNextWord = token.length === 1 && CZECH_ONE_LETTER_PREPOSITIONS.has(token.toLocaleLowerCase("cs"));
149
+ while (index < text.length) {
150
+ const whitespace = firstCodePoint(text, index);
151
+ if (whitespace === void 0 || !/\s/u.test(whitespace)) break;
152
+ index += whitespace.length;
153
+ if (protectsNextWord) breaks.add(index);
154
+ }
155
+ tokenStart = index;
156
+ }
157
+ return breaks;
158
+ };
159
+ const allowsBreak = (text, index, policy) => {
160
+ const previous = previousCodePoint(text, index);
161
+ const next = firstCodePoint(text, index);
162
+ return !isProhibitedLineEnd(previous, policy) && !isProhibitedLineStart(next, policy);
163
+ };
164
+ const pushBreak = (breaks, text, index, policy) => {
165
+ if (index <= 0 || index > text.length || breaks.at(-1) === index) return;
166
+ if (allowsBreak(text, index, policy)) breaks.push(index);
167
+ };
168
+ const findUnicodeGraphemeBreaks = (text, policy) => {
169
+ const breaks = [];
170
+ for (const segment of getSegmenter(graphemeSegmenters, "grapheme", policy?.locale).segment(text)) {
171
+ const end = segment.index + segment.segment.length;
172
+ breaks.push(end);
173
+ }
174
+ return breaks;
175
+ };
176
+ const findUnicodeBreaks = (text, policy) => {
177
+ if (text.length === 0) return [];
178
+ const breaks = [];
179
+ const graphemeBreaks = findUnicodeGraphemeBreaks(text, policy);
180
+ for (const index of graphemeBreaks) {
181
+ const previous = previousCodePoint(text, index);
182
+ if (previous !== void 0 && (/\s/u.test(previous) || BREAK_AFTER_CHARACTER.has(previous) || isLegacyEthiopicBreakCharacter(previous, policy))) pushBreak(breaks, text, index, policy);
183
+ }
184
+ const wordSegments = [...getSegmenter(wordSegmenters, "word", policy?.locale).segment(text)];
185
+ for (const [segmentIndex, segment] of wordSegments.entries()) {
186
+ const end = segment.index + segment.segment.length;
187
+ const nextSegment = wordSegments[segmentIndex + 1];
188
+ if (segment.isWordLike && nextSegment?.isWordLike === true && DICTIONARY_BREAK_SCRIPT.test(segment.segment + nextSegment.segment)) pushBreak(breaks, text, end, policy);
189
+ }
190
+ if (CJK_SCRIPT.test(text)) for (const index of graphemeBreaks) {
191
+ const previous = previousCodePoint(text, index);
192
+ if (isCjkLineBreakParticipant(previous, policy)) pushBreak(breaks, text, index, policy);
193
+ }
194
+ const protectedBreaks = czechProtectedBreaks(text, policy);
195
+ return [...new Set(breaks)].filter((index) => !protectedBreaks.has(index)).sort((left, right) => left - right);
196
+ };
197
+ const hyphenatorFor = (locale) => {
198
+ const normalized = locale?.trim().replaceAll("_", "-").toLowerCase();
199
+ if (!normalized) return;
200
+ if (normalized === "en-gb" || normalized.startsWith("en-gb-")) return hyphenateBritishEnglish;
201
+ if (normalized === "en-us" || normalized.startsWith("en-us-")) return hyphenateAmericanEnglish;
202
+ if (normalized === "cs" || normalized.startsWith("cs-")) return hyphenateCzech;
203
+ if (normalized === "sk" || normalized.startsWith("sk-")) return hyphenateSlovak;
204
+ };
205
+ const isAllCapsWord = (text, locale) => {
206
+ const letters = [...text].filter((character) => /\p{Letter}/u.test(character)).join("");
207
+ if (letters.length === 0) return false;
208
+ const casingLocale = segmenterLocale(locale);
209
+ return letters === letters.toLocaleUpperCase(casingLocale) && letters !== letters.toLocaleLowerCase(casingLocale);
210
+ };
211
+ const findPatternHyphenationBreaks = (text, policy) => {
212
+ if (text.length > 256 || text.includes(SOFT_HYPHEN)) return [];
213
+ const hyphenate = hyphenatorFor(policy?.locale);
214
+ if (!hyphenate) return [];
215
+ if (policy?.doNotHyphenateCaps && (policy.renderedAllCaps === true || isAllCapsWord(text, policy.locale))) return [];
216
+ const hyphenated = hyphenate(text);
217
+ const breaks = [];
218
+ let sourceOffset = 0;
219
+ for (const character of hyphenated) {
220
+ if (character === SOFT_HYPHEN) {
221
+ breaks.push(sourceOffset);
222
+ continue;
223
+ }
224
+ sourceOffset += character.length;
225
+ }
226
+ return sourceOffset === text.length ? breaks : [];
227
+ };
228
+ const isDefaultHangingPunctuation = (text, policy) => {
229
+ const characters = [...text];
230
+ return characters.length > 0 && characters.every((character) => isProhibitedLineStart(character, policy));
231
+ };
232
+ const defaultLineBreakProvider = {
233
+ findBreaks: findUnicodeBreaks,
234
+ findGraphemeBreaks: findUnicodeGraphemeBreaks,
235
+ findHyphenationBreaks: findPatternHyphenationBreaks,
236
+ isHangingPunctuation: isDefaultHangingPunctuation
237
+ };
238
+ let activeLineBreakProvider = defaultLineBreakProvider;
239
+ let lineBreakProviderGeneration = 0;
240
+ const getLineBreakProvider = () => activeLineBreakProvider;
241
+ /** Changes whenever the active provider changes, so layout caches cannot go stale. */
242
+ const getLineBreakProviderGeneration = () => lineBreakProviderGeneration;
243
+ const setLineBreakProvider = (provider) => {
244
+ activeLineBreakProvider = provider;
245
+ lineBreakProviderGeneration += 1;
246
+ };
247
+ const resetLineBreakProvider = () => {
248
+ activeLineBreakProvider = defaultLineBreakProvider;
249
+ lineBreakProviderGeneration += 1;
250
+ };
251
+ //#endregion
252
+ export { MAX_HYPHENATION_WORD_LENGTH, defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGeneration, resetLineBreakProvider, setLineBreakProvider };
@@ -1,10 +1,15 @@
1
+ import { LineBreakPolicy } from "./lineBreakProvider.js";
2
+
1
3
  //#region src/layout-engine/measure/lineBreaks.d.ts
2
4
  /**
3
5
  * Indices in `text` where a line may end. Latin text breaks after
4
6
  * whitespace or hyphen; CJK text can break after every ideograph so a
5
7
  * partial line is filled instead of pushing the whole run to the next line.
6
8
  */
7
- declare function findWordBreaks(text: string): number[];
9
+ declare const findWordBreaks: (text: string, policy?: LineBreakPolicy) => number[];
10
+ declare const findGraphemeBreaks: (text: string, policy?: LineBreakPolicy) => number[];
11
+ declare const findHyphenationBreaks: (text: string, policy?: LineBreakPolicy) => number[];
12
+ declare const isHangingPunctuation: (text: string, policy?: LineBreakPolicy) => boolean;
8
13
  declare function isBreakChar(char: string | undefined): boolean;
9
14
  //#endregion
10
- export { findWordBreaks, isBreakChar };
15
+ export { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation };
@@ -1,3 +1,4 @@
1
+ import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
1
2
  import { isCjkCodePoint } from "../../utils/scriptSegments.js";
2
3
  //#region src/layout-engine/measure/lineBreaks.ts
3
4
  /**
@@ -5,26 +6,18 @@ import { isCjkCodePoint } from "../../utils/scriptSegments.js";
5
6
  * whitespace or hyphen; CJK text can break after every ideograph so a
6
7
  * partial line is filled instead of pushing the whole run to the next line.
7
8
  */
8
- function findWordBreaks(text) {
9
- const breaks = [];
10
- for (let index = 0; index < text.length;) {
11
- const codePoint = text.codePointAt(index);
12
- if (codePoint === void 0) break;
13
- const charLength = codePoint > 65535 ? 2 : 1;
14
- const char = text[index];
15
- if (char === " " || char === "-" || char === " ") breaks.push(index + charLength);
16
- else if (isCjkCodePoint(codePoint)) breaks.push(index + charLength);
17
- index += charLength;
18
- }
19
- return breaks;
20
- }
9
+ const findWordBreaks = (text, policy) => getLineBreakProvider().findBreaks(text, policy);
10
+ const findGraphemeBreaks = (text, policy) => getLineBreakProvider().findGraphemeBreaks(text, policy);
11
+ const findHyphenationBreaks = (text, policy) => getLineBreakProvider().findHyphenationBreaks?.(text, policy) ?? [];
12
+ const isHangingPunctuation = (text, policy) => {
13
+ return (getLineBreakProvider().isHangingPunctuation ?? defaultLineBreakProvider.isHangingPunctuation)?.(text, policy) ?? false;
14
+ };
21
15
  function isBreakChar(char) {
22
16
  if (char === void 0) return false;
23
- if (char === " " || char === "-" || char === " ") return true;
24
- const codePoint = char.codePointAt(0);
25
- if (codePoint === void 0) return false;
17
+ if (/\s/u.test(char) || char === "-" || char === "­" || char === "") return true;
26
18
  if (char >= "\udc00" && char <= "\udfff") return true;
27
- return isCjkCodePoint(codePoint);
19
+ const codePoint = char.codePointAt(0);
20
+ return codePoint !== void 0 && isCjkCodePoint(codePoint);
28
21
  }
29
22
  //#endregion
30
- export { findWordBreaks, isBreakChar };
23
+ export { findGraphemeBreaks, findHyphenationBreaks, findWordBreaks, isBreakChar, isHangingPunctuation };
@@ -16,6 +16,7 @@ declare const DEFAULT_TAB_STOP_TWIPS = 720;
16
16
  declare function resolveListMarkerFont(block: ParagraphBlock): {
17
17
  fontFamily: string;
18
18
  fontSize: number;
19
+ bold?: boolean;
19
20
  };
20
21
  /**
21
22
  * Compute the marker's inline-block width in pixels, or 0 if the paragraph
@@ -19,9 +19,13 @@ const TWIPS_TO_PX = 96 / 1440;
19
19
  function resolveListMarkerFont(block) {
20
20
  const attrs = block.attrs;
21
21
  const firstTextRun = block.runs.find((r) => r.kind === "text");
22
+ const fontFamily = attrs?.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
23
+ const fontSize = attrs?.listMarkerFontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
24
+ const bold = attrs?.listMarkerBold ?? firstTextRun?.bold;
22
25
  return {
23
- fontFamily: attrs?.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY,
24
- fontSize: attrs?.listMarkerFontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE
26
+ fontFamily,
27
+ fontSize,
28
+ ...bold !== void 0 ? { bold } : {}
25
29
  };
26
30
  }
27
31
  /**
@@ -45,10 +49,11 @@ function resolveListMarkerFont(block) {
45
49
  function getListMarkerInlineWidth(block) {
46
50
  const attrs = block.attrs;
47
51
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
48
- const { fontFamily, fontSize } = resolveListMarkerFont(block);
52
+ const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
49
53
  const style = {
50
54
  fontFamily,
51
- fontSize
55
+ fontSize,
56
+ ...bold !== void 0 ? { bold } : {}
52
57
  };
53
58
  const naturalWidth = measureTextWidth(attrs.listMarker, style);
54
59
  const markerEndOffset = getMarkerEndOffset(naturalWidth, attrs.listMarkerAlignment);
@@ -77,10 +82,11 @@ function getListMarkerInlineWidth(block) {
77
82
  function getListMarkerVisualOffset(block) {
78
83
  const attrs = block.attrs;
79
84
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
80
- const { fontFamily, fontSize } = resolveListMarkerFont(block);
85
+ const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
81
86
  const naturalWidth = measureTextWidth(attrs.listMarker, {
82
87
  fontFamily,
83
- fontSize
88
+ fontSize,
89
+ ...bold !== void 0 ? { bold } : {}
84
90
  });
85
91
  if (attrs.listMarkerAlignment === "right") return -naturalWidth;
86
92
  if (attrs.listMarkerAlignment === "center") return -naturalWidth / 2;
@@ -1,7 +1,6 @@
1
1
  import { FlowBlock, Measure, TableBlock, TableMeasure, TextBoxBlock, TextBoxMeasure } from "../types.js";
2
2
  import { FloatingImageZone } from "./floatingZones.js";
3
3
  //#region src/layout-engine/measure/measureBlocks.d.ts
4
- declare function measureTableCellBlockVisualHeight(block: FlowBlock, blockMeasure: Measure): number;
5
4
  declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, fieldValues?: ReadonlyMap<number, string>): TableMeasure;
6
5
  type BandPageGeometry = {
7
6
  pageWidth?: number | number[];
@@ -25,4 +24,4 @@ declare function measureTextBoxBlock(tb: TextBoxBlock, fieldValues?: ReadonlyMap
25
24
  declare function measureBlocks(blocks: FlowBlock[], contentWidth: number | number[], marginTop?: number | number[], pageGeometry?: BandPageGeometry, fieldValues?: ReadonlyMap<number, string>): Measure[];
26
25
  declare function measureSingleBlockWithoutFloatingZones(block: FlowBlock, blockWidth: number, blockIndex: number): Measure;
27
26
  //#endregion
28
- export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTableCellBlockVisualHeight, measureTextBoxBlock };
27
+ export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
@@ -1,6 +1,7 @@
1
- import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
1
+ import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
2
2
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
3
3
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
4
+ import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
4
5
  import { layoutTextBoxParagraphs } from "./textBoxParagraphLayout.js";
5
6
  import { getTextBoxGroupId } from "../textBoxGroup.js";
6
7
  import { isParagraphFrameTextBox } from "../paragraphFrame.js";
@@ -52,39 +53,6 @@ function resolveTableWidthPx(width, widthType, contentWidth) {
52
53
  if (widthType === "pct") return contentWidth * width / 5e3;
53
54
  if (widthType === "dxa" || !widthType || widthType === "auto") return Math.round(width / 20 * 1.333);
54
55
  }
55
- function isInlineFlowImageRun(run) {
56
- if (run.displayMode === "float") return false;
57
- if (run.wrapType === "square" || run.wrapType === "tight" || run.wrapType === "through" || run.wrapType === "behind" || run.wrapType === "inFront") return false;
58
- if (run.displayMode === "block" || run.wrapType === "topAndBottom") return false;
59
- return true;
60
- }
61
- function measureTableCellBlockVisualHeight(block, blockMeasure) {
62
- if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
63
- if (block.kind !== "paragraph" || blockMeasure.kind !== "paragraph") {
64
- if ("totalHeight" in blockMeasure) return blockMeasure.totalHeight;
65
- if ("height" in blockMeasure) return blockMeasure.height;
66
- return 0;
67
- }
68
- const paragraphBlock = block;
69
- const paragraphMeasure = blockMeasure;
70
- const nonEmptyRuns = paragraphBlock.runs.filter((run) => run.kind !== "text" || run.text.replace(/\u00a0/gu, " ").trim().length > 0);
71
- if (paragraphMeasure.lines.length !== 1 || nonEmptyRuns.length === 0) return paragraphMeasure.totalHeight;
72
- const inlineImageRuns = [];
73
- for (const run of nonEmptyRuns) {
74
- if (run.kind !== "image" || !isInlineFlowImageRun(run)) return paragraphMeasure.totalHeight;
75
- inlineImageRuns.push(run);
76
- }
77
- let maxImageHeight = 0;
78
- for (const run of inlineImageRuns) maxImageHeight = Math.max(maxImageHeight, run.height);
79
- if (inlineImageRuns.length === 1) {
80
- const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
81
- const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
82
- return spacingBefore + maxImageHeight + spacingAfter;
83
- }
84
- const spacingBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
85
- const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
86
- return spacingBefore + maxImageHeight + spacingAfter;
87
- }
88
56
  function measureTableBlock(tableBlock, contentWidth, fieldValues) {
89
57
  const DEFAULT_CELL_PADDING_X = 7;
90
58
  const DEFAULT_CELL_PADDING_Y = 0;
@@ -147,12 +115,14 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
147
115
  const cell = row.cells[cellIdx];
148
116
  const sourceCell = sourceRowCells?.[cellIdx];
149
117
  cell.height = 0;
118
+ const flowState = createTableCellFlowState();
150
119
  for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
151
120
  const sourceBlock = sourceCell?.blocks[blockIdx];
152
121
  const blockMeasure = cell.blocks[blockIdx];
153
122
  if (!sourceBlock || !blockMeasure) continue;
154
- cell.height += measureTableCellBlockVisualHeight(sourceBlock, blockMeasure);
123
+ placeTableCellBlock(flowState, sourceBlock, blockMeasure);
155
124
  }
125
+ cell.height = finishTableCellFlow(flowState);
156
126
  const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
157
127
  const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
158
128
  cell.height += padTop + padBottom;
@@ -576,4 +546,4 @@ function measureSingleBlockWithoutFloatingZones(block, blockWidth, blockIndex) {
576
546
  return measureBlock(block, blockWidth);
577
547
  }
578
548
  //#endregion
579
- export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTableCellBlockVisualHeight, measureTextBoxBlock };
549
+ export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };