@stll/folio-core 0.12.0 → 0.14.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 (138) hide show
  1. package/dist/ai-edits/apply.d.ts +2 -1
  2. package/dist/ai-edits/apply.js +118 -40
  3. package/dist/ai-edits/blockRange.js +1 -1
  4. package/dist/ai-edits/clean-text.js +2 -1
  5. package/dist/ai-edits/headless.js +3 -3
  6. package/dist/ai-edits/snapshot.js +21 -3
  7. package/dist/ai-edits/table-cell-mutations.js +2 -1
  8. package/dist/ai-edits/table-row-column-mutations.d.ts +9 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +1 -1
  10. package/dist/ai-edits/types.d.ts +28 -1
  11. package/dist/ai-edits/word-diff.js +21 -0
  12. package/dist/compat/eigenpal.d.ts +3 -3
  13. package/dist/compat/eigenpal.js +3 -3
  14. package/dist/controller/headerFooterEditorManager.js +1 -1
  15. package/dist/controller/hiddenEditorManager.js +12 -1
  16. package/dist/controller/layoutPipeline.js +1 -1
  17. package/dist/controller/noteEditorManager.js +1 -1
  18. package/dist/document-operations.d.ts +13 -13
  19. package/dist/document-operations.js +31 -21
  20. package/dist/docx/blockContentParser.js +19 -5
  21. package/dist/docx/commentRangeIntegrity.d.ts +6 -0
  22. package/dist/docx/commentRangeIntegrity.js +186 -0
  23. package/dist/docx/conformance.js +57 -3
  24. package/dist/docx/encryption/agileDecryption.js +12 -2
  25. package/dist/docx/encryption/compoundFile.js +6 -1
  26. package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
  27. package/dist/docx/encryption/encryptionInfo.js +18 -2
  28. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  29. package/dist/docx/ensureParaIds.js +7 -6
  30. package/dist/docx/groupDrawingParser.js +10 -5
  31. package/dist/docx/hyperlinkParser.js +1 -1
  32. package/dist/docx/imageParser.js +10 -6
  33. package/dist/docx/metadataPrivacy.js +17 -3
  34. package/dist/docx/paragraphParser.js +7 -3
  35. package/dist/docx/rezip.js +19 -8
  36. package/dist/docx/runParser.js +13 -4
  37. package/dist/docx/sectionParser.js +17 -4
  38. package/dist/docx/selectiveSave.js +4 -0
  39. package/dist/docx/selectiveXmlPatch.d.ts +22 -1
  40. package/dist/docx/selectiveXmlPatch.js +61 -1
  41. package/dist/docx/serializer/blockSdtSerializer.js +3 -2
  42. package/dist/docx/serializer/commentSerializer.js +4 -4
  43. package/dist/docx/serializer/paragraphSerializer.js +16 -14
  44. package/dist/docx/serializer/runSerializer.js +21 -19
  45. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
  46. package/dist/docx/serializer/tableSerializer.js +9 -7
  47. package/dist/docx/serializer/xmlUtils.d.ts +13 -1
  48. package/dist/docx/serializer/xmlUtils.js +27 -1
  49. package/dist/docx/server/docxToMarkdown.d.ts +15 -0
  50. package/dist/docx/server/docxToMarkdown.js +17 -0
  51. package/dist/docx/server/extractDocxText.js +55 -8
  52. package/dist/docx/settingsParser.js +15 -2
  53. package/dist/docx/tableParser.d.ts +8 -1
  54. package/dist/docx/tableParser.js +29 -8
  55. package/dist/docx/unzip.js +1 -0
  56. package/dist/docx/vmlImageParser.js +11 -3
  57. package/dist/docx/xmlParser.js +11 -1
  58. package/dist/fonts/embeddedFonts.d.ts +33 -5
  59. package/dist/fonts/embeddedFonts.js +37 -8
  60. package/dist/index.d.ts +3 -3
  61. package/dist/index.js +3 -3
  62. package/dist/layout-bridge/convert/toFlowBlocks.js +15 -0
  63. package/dist/layout-engine/index.js +19 -12
  64. package/dist/layout-engine/measure/cache.js +2 -0
  65. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
  66. package/dist/layout-engine/measure/floatingTablePosition.d.ts +9 -0
  67. package/dist/layout-engine/measure/floatingTablePosition.js +17 -0
  68. package/dist/layout-engine/measure/index.d.ts +2 -1
  69. package/dist/layout-engine/measure/index.js +2 -1
  70. package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
  71. package/dist/layout-engine/measure/lineBreakProvider.js +3 -3
  72. package/dist/layout-engine/measure/measureBlocks.js +11 -8
  73. package/dist/layout-engine/measure/measureHelpers.d.ts +10 -1
  74. package/dist/layout-engine/measure/measureHelpers.js +12 -1
  75. package/dist/layout-engine/measure/measureParagraph.js +16 -0
  76. package/dist/layout-engine/types.d.ts +22 -2
  77. package/dist/layout-painter/renderImage.d.ts +12 -1
  78. package/dist/layout-painter/renderImage.js +21 -4
  79. package/dist/layout-painter/renderPage.js +5 -8
  80. package/dist/layout-painter/renderParagraph.js +33 -9
  81. package/dist/layout-painter/renderTable.js +2 -1
  82. package/dist/layout-painter/renderWatermark.d.ts +1 -1
  83. package/dist/layout-painter/renderWatermark.js +2 -1
  84. package/dist/markdown/fromMarkdown.js +1 -1
  85. package/dist/prosemirror/attrs/index.d.ts +1 -1
  86. package/dist/prosemirror/attrs/index.js +21 -0
  87. package/dist/prosemirror/commands/comments.d.ts +76 -1
  88. package/dist/prosemirror/commands/comments.js +401 -28
  89. package/dist/prosemirror/commands/hyperlink.js +15 -1
  90. package/dist/prosemirror/commands/image.js +7 -1
  91. package/dist/prosemirror/commands/index.d.ts +2 -2
  92. package/dist/prosemirror/commands/index.js +2 -2
  93. package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
  94. package/dist/prosemirror/commentIdAllocator.js +13 -3
  95. package/dist/prosemirror/conversion/fromProseDoc.js +122 -5
  96. package/dist/prosemirror/conversion/index.js +1 -1
  97. package/dist/prosemirror/conversion/toProseDoc.js +20 -8
  98. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
  99. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
  100. package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
  101. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
  102. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +49 -13
  103. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  104. package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -3
  105. package/dist/prosemirror/index.d.ts +1 -1
  106. package/dist/prosemirror/index.js +1 -1
  107. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +2 -2
  108. package/dist/prosemirror/plugins/contentControlWidgets.js +4 -4
  109. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
  110. package/dist/prosemirror/plugins/revisionIds.js +57 -0
  111. package/dist/prosemirror/plugins/suggestionMode.js +4 -3
  112. package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
  113. package/dist/prosemirror/runFormattingMarkNames.js +40 -0
  114. package/dist/prosemirror/schema/index.d.ts +1 -1
  115. package/dist/prosemirror/schema/marks.d.ts +27 -4
  116. package/dist/prosemirror/schema/marks.js +15 -0
  117. package/dist/prosemirror/schema/nodes.d.ts +50 -2
  118. package/dist/prosemirror/utils/tabCalculator.js +3 -3
  119. package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
  120. package/dist/server.d.ts +3 -2
  121. package/dist/server.js +4 -3
  122. package/dist/utils/clipboard.d.ts +10 -1
  123. package/dist/utils/clipboard.js +33 -6
  124. package/dist/utils/colorResolver.d.ts +8 -1
  125. package/dist/utils/colorResolver.js +35 -5
  126. package/dist/utils/fontResolver.d.ts +2 -1
  127. package/dist/utils/fontResolver.js +9 -3
  128. package/dist/utils/hexId.d.ts +11 -1
  129. package/dist/utils/hexId.js +11 -1
  130. package/dist/utils/sanitizeImageSrc.d.ts +21 -0
  131. package/dist/utils/sanitizeImageSrc.js +30 -0
  132. package/dist/utils/stripXmlDeclarations.d.ts +11 -0
  133. package/dist/utils/stripXmlDeclarations.js +35 -0
  134. package/dist/version-comparison.d.ts +22 -2
  135. package/dist/version-comparison.js +41 -12
  136. package/dist/watermark/index.d.ts +11 -1
  137. package/dist/watermark/index.js +22 -1
  138. package/package.json +3 -2
@@ -500,6 +500,13 @@ function findAllDeep(root, namespace, localName) {
500
500
  return results;
501
501
  }
502
502
  /**
503
+ * Sanity cap on distinct `xmlns:*` declarations collected per element. Real
504
+ * documents declare a handful; a hostile element with thousands of unique
505
+ * prefixes would otherwise get replayed onto every captured `w:pict` rawXml
506
+ * subtree that inherits from it.
507
+ */
508
+ const MAX_XMLNS_DECLARATIONS_PER_ELEMENT = 64;
509
+ /**
503
510
  * Collect every `xmlns` / `xmlns:*` declaration from an element's attributes.
504
511
  *
505
512
  * The serializer's hard-coded root namespaces only cover canonical prefixes
@@ -512,7 +519,10 @@ function collectXmlnsDeclarations(element) {
512
519
  const out = {};
513
520
  const attrs = element.attributes;
514
521
  if (!attrs) return out;
515
- for (const [key, value] of Object.entries(attrs)) if ((key === "xmlns" || key.startsWith("xmlns:")) && value !== void 0) out[key] = String(value);
522
+ for (const [key, value] of Object.entries(attrs)) {
523
+ if (Object.keys(out).length >= MAX_XMLNS_DECLARATIONS_PER_ELEMENT) break;
524
+ if ((key === "xmlns" || key.startsWith("xmlns:")) && value !== void 0) out[key] = String(value);
525
+ }
516
526
  return out;
517
527
  }
518
528
  /**
@@ -15,9 +15,24 @@
15
15
  *
16
16
  * Ported from eigenpal/docx-editor (see NOTICE.md).
17
17
  */
18
+ /**
19
+ * Build the per-document scoped family name an embedded face registers
20
+ * under. `docNonce` is fresh per document load (see {@link getEmbeddedFontFaces}),
21
+ * so the same original name in two different documents (or two loads of the
22
+ * same document) never collides.
23
+ */
24
+ declare function scopeEmbeddedFontFamily(docNonce: string, originalFamily: string): string;
18
25
  /** A single de-obfuscated embedded font face, ready to register as `@font-face`. */
19
26
  type EmbeddedFont = {
20
- /** Word font name to register the face under (`w:font w:name`). */family: string; /** CSS `font-style` the face maps to (`embed*Italic` → `italic`). */
27
+ /**
28
+ * Family name to register the face under (`@font-face`/`FontFace`).
29
+ * Always a per-document SCOPED name (see {@link scopeEmbeddedFontFamily}) —
30
+ * never the raw `w:font w:name` from the DOCX. Use `originalFamily` (and
31
+ * {@link buildEmbeddedFontFamilyMap}) to resolve document runs, which
32
+ * still reference the font by its original DOCX name, to this family.
33
+ */
34
+ family: string; /** Original Word font name (`w:font w:name`), before scoping. */
35
+ originalFamily: string; /** CSS `font-style` the face maps to (`embed*Italic` → `italic`). */
21
36
  style: "normal" | "italic"; /** CSS `font-weight` the face maps to (`embedBold*` → `700`). */
22
37
  weight: 400 | 700; /** De-obfuscated OpenType/TrueType bytes (backed by a fresh, non-shared buffer). */
23
38
  bytes: Uint8Array<ArrayBuffer>; /** Whether the source face was subsetted (`w:subsetted`). */
@@ -33,14 +48,27 @@ type EmbeddedFontParts = {
33
48
  * Resolve and de-obfuscate every embedded font face declared in a font table.
34
49
  * Pure and DOM-free. Faces whose relationship or binary is missing are skipped,
35
50
  * so the result only contains faces that produced usable bytes.
51
+ *
52
+ * `docNonce` scopes every face's registered family to one document load (see
53
+ * {@link scopeEmbeddedFontFamily}); defaults to a fresh random id so callers
54
+ * that don't care about a specific value still get per-call isolation. Pass
55
+ * an explicit value for deterministic tests.
56
+ */
57
+ declare function getEmbeddedFontFaces(parts: EmbeddedFontParts, docNonce?: string): EmbeddedFont[];
58
+ /**
59
+ * Build the original DOCX font name → scoped registered family map for a set
60
+ * of resolved embedded faces. Callers thread this into font resolution (see
61
+ * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) so the document's own
62
+ * runs — which still carry the original name — keep resolving to the scoped
63
+ * face that was actually registered on `document.fonts`.
36
64
  */
37
- declare function getEmbeddedFontFaces(parts: EmbeddedFontParts): EmbeddedFont[];
65
+ declare function buildEmbeddedFontFamilyMap(faces: readonly EmbeddedFont[]): Map<string, string>;
38
66
  /**
39
67
  * Extract every embedded font face from a raw `.docx` buffer. Unzips the
40
68
  * package, then resolves + de-obfuscates the faces via
41
69
  * {@link getEmbeddedFontFaces}. Returns an empty list for documents with no
42
- * embedded fonts.
70
+ * embedded fonts. See {@link getEmbeddedFontFaces} for `docNonce`.
43
71
  */
44
- declare function extractEmbeddedFonts(buffer: ArrayBuffer): Promise<EmbeddedFont[]>;
72
+ declare function extractEmbeddedFonts(buffer: ArrayBuffer, docNonce?: string): Promise<EmbeddedFont[]>;
45
73
  //#endregion
46
- export { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces };
74
+ export { EmbeddedFont, EmbeddedFontParts, buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily };
@@ -1,4 +1,5 @@
1
1
  import { parseRelationships, resolveRelativePath } from "../docx/relsParser.js";
2
+ import { generateHexId } from "../utils/hexId.js";
2
3
  import { unzipDocx } from "../docx/unzip.js";
3
4
  import { deobfuscateFont, isValidFontKey } from "./fontDeobfuscation.js";
4
5
  import { parseEmbeddedFontTable } from "./embeddedFontTable.js";
@@ -19,6 +20,16 @@ import { parseEmbeddedFontTable } from "./embeddedFontTable.js";
19
20
  *
20
21
  * Ported from eigenpal/docx-editor (see NOTICE.md).
21
22
  */
23
+ const SCOPED_FAMILY_PREFIX = "folio-embedded";
24
+ /**
25
+ * Build the per-document scoped family name an embedded face registers
26
+ * under. `docNonce` is fresh per document load (see {@link getEmbeddedFontFaces}),
27
+ * so the same original name in two different documents (or two loads of the
28
+ * same document) never collides.
29
+ */
30
+ function scopeEmbeddedFontFamily(docNonce, originalFamily) {
31
+ return `${SCOPED_FAMILY_PREFIX}-${docNonce}-${originalFamily}`;
32
+ }
22
33
  /** Pairs each embed field with the CSS weight/style it renders as. */
23
34
  const EMBED_KINDS = [
24
35
  {
@@ -57,7 +68,7 @@ function lookupFontData(resolvedPath, fonts) {
57
68
  const wanted = normalizePackagePath(resolvedPath);
58
69
  for (const [path, data] of fonts) if (normalizePackagePath(path) === wanted) return data;
59
70
  }
60
- function resolveFace(family, ref, kind, fonts, rels) {
71
+ function resolveFace(originalFamily, ref, kind, fonts, rels, docNonce) {
61
72
  const target = rels.get(ref.relId)?.target;
62
73
  if (!target) return null;
63
74
  const raw = lookupFontData(resolveRelativePath(FONTTABLE_RELS_BASE_PATH, target), fonts);
@@ -65,7 +76,8 @@ function resolveFace(family, ref, kind, fonts, rels) {
65
76
  const bytes = new Uint8Array(raw);
66
77
  const data = ref.fontKey && isValidFontKey(ref.fontKey) ? deobfuscateFont(bytes, ref.fontKey) : bytes;
67
78
  return {
68
- family,
79
+ family: scopeEmbeddedFontFamily(docNonce, originalFamily),
80
+ originalFamily,
69
81
  style: kind.style,
70
82
  weight: kind.weight,
71
83
  bytes: data,
@@ -76,8 +88,13 @@ function resolveFace(family, ref, kind, fonts, rels) {
76
88
  * Resolve and de-obfuscate every embedded font face declared in a font table.
77
89
  * Pure and DOM-free. Faces whose relationship or binary is missing are skipped,
78
90
  * so the result only contains faces that produced usable bytes.
91
+ *
92
+ * `docNonce` scopes every face's registered family to one document load (see
93
+ * {@link scopeEmbeddedFontFamily}); defaults to a fresh random id so callers
94
+ * that don't care about a specific value still get per-call isolation. Pass
95
+ * an explicit value for deterministic tests.
79
96
  */
80
- function getEmbeddedFontFaces(parts) {
97
+ function getEmbeddedFontFaces(parts, docNonce = generateHexId()) {
81
98
  const entries = parseEmbeddedFontTable(parts.fontTableXml);
82
99
  if (entries.length === 0) return [];
83
100
  if (!parts.fontTableRelsXml || parts.fonts.size === 0) return [];
@@ -86,11 +103,23 @@ function getEmbeddedFontFaces(parts) {
86
103
  for (const entry of entries) for (const kind of EMBED_KINDS) {
87
104
  const ref = entry[kind.ref];
88
105
  if (!ref) continue;
89
- const face = resolveFace(entry.name, ref, kind, parts.fonts, rels);
106
+ const face = resolveFace(entry.name, ref, kind, parts.fonts, rels, docNonce);
90
107
  if (face) faces.push(face);
91
108
  }
92
109
  return faces;
93
110
  }
111
+ /**
112
+ * Build the original DOCX font name → scoped registered family map for a set
113
+ * of resolved embedded faces. Callers thread this into font resolution (see
114
+ * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) so the document's own
115
+ * runs — which still carry the original name — keep resolving to the scoped
116
+ * face that was actually registered on `document.fonts`.
117
+ */
118
+ function buildEmbeddedFontFamilyMap(faces) {
119
+ const map = /* @__PURE__ */ new Map();
120
+ for (const face of faces) map.set(face.originalFamily, face.family);
121
+ return map;
122
+ }
94
123
  function findFontTableRels(allXml) {
95
124
  for (const [path, xml] of allXml) if (normalizePackagePath(path) === FONTTABLE_RELS_PATH) return xml;
96
125
  }
@@ -98,15 +127,15 @@ function findFontTableRels(allXml) {
98
127
  * Extract every embedded font face from a raw `.docx` buffer. Unzips the
99
128
  * package, then resolves + de-obfuscates the faces via
100
129
  * {@link getEmbeddedFontFaces}. Returns an empty list for documents with no
101
- * embedded fonts.
130
+ * embedded fonts. See {@link getEmbeddedFontFaces} for `docNonce`.
102
131
  */
103
- async function extractEmbeddedFonts(buffer) {
132
+ async function extractEmbeddedFonts(buffer, docNonce = generateHexId()) {
104
133
  const raw = await unzipDocx(buffer);
105
134
  return getEmbeddedFontFaces({
106
135
  fontTableXml: raw.fontTableXml,
107
136
  fontTableRelsXml: findFontTableRels(raw.allXml),
108
137
  fonts: raw.fonts
109
- });
138
+ }, docNonce);
110
139
  }
111
140
  //#endregion
112
- export { extractEmbeddedFonts, getEmbeddedFontFaces };
141
+ export { buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily };
package/dist/index.d.ts CHANGED
@@ -27,9 +27,9 @@ import { AcceptAutocompleteResult, AutocompleteSuggestionPluginOptions, Autocomp
27
27
  import { ImageMeta, ImageRef, MarkdownOptions, MarkdownResult } from "./markdown/types.js";
28
28
  import { fromMarkdown } from "./markdown/fromMarkdown.js";
29
29
  import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
30
- import { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
31
- import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
30
+ import { EmbeddedFont, EmbeddedFontParts, buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily } from "./fonts/embeddedFonts.js";
31
+ import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "./utils/fontResolver.js";
32
32
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
33
33
  type Document = document_d_exports.Document;
34
34
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
35
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
35
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "./utils/fontResolver.js";
1
2
  import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
2
3
  import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
3
- import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
4
4
  import { diffWordSegments } from "./ai-edits/word-diff.js";
5
5
  import { applyFolioAIEditOperations } from "./ai-edits/apply.js";
6
6
  import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
@@ -25,6 +25,6 @@ import { clearTemplateSlashMenu, consumeTemplateSlashQuery, getTemplateSlashMenu
25
25
  import { DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acceptAutocompleteWord, appendAutocompleteToken, autocompleteSuggestionKey, autocompleteSuggestionPlugin, clearAutocompleteSuggestion, finishAutocompleteSuggestion, getAutocompleteSuggestion, shouldTriggerAutocomplete, startAutocompleteSuggestion } from "./prosemirror/plugins/autocompleteSuggestion.js";
26
26
  import { fromMarkdown } from "./markdown/fromMarkdown.js";
27
27
  import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
28
- import { extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
28
+ import { buildEmbeddedFontFamilyMap, extractEmbeddedFonts, getEmbeddedFontFaces, scopeEmbeddedFontFamily } from "./fonts/embeddedFonts.js";
29
29
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
30
- export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
30
+ export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -333,6 +333,10 @@ function extractRunFormatting(marks, theme) {
333
333
  formatting.changeAuthor = attrs.author;
334
334
  if (attrs.date !== void 0) formatting.changeDate = attrs.date;
335
335
  formatting.changeRevisionId = attrs.revisionId;
336
+ if (attrs.provenance === "suggested") {
337
+ formatting.isSuggestion = true;
338
+ if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
339
+ }
336
340
  break;
337
341
  }
338
342
  case "deletion": {
@@ -341,6 +345,10 @@ function extractRunFormatting(marks, theme) {
341
345
  formatting.changeAuthor = attrs.author;
342
346
  if (attrs.date !== void 0) formatting.changeDate = attrs.date;
343
347
  formatting.changeRevisionId = attrs.revisionId;
348
+ if (attrs.provenance === "suggested") {
349
+ formatting.isSuggestion = true;
350
+ if (attrs.suggestionId) formatting.suggestionId = attrs.suggestionId;
351
+ }
344
352
  break;
345
353
  }
346
354
  default: break;
@@ -455,6 +463,9 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
455
463
  if (attrs.cropRight != null) run.cropRight = attrs.cropRight;
456
464
  if (attrs.cropBottom != null) run.cropBottom = attrs.cropBottom;
457
465
  if (attrs.cropLeft != null) run.cropLeft = attrs.cropLeft;
466
+ if (attrs.borderWidth != null) run.borderWidth = attrs.borderWidth;
467
+ if (attrs.borderColor) run.borderColor = attrs.borderColor;
468
+ if (attrs.borderStyle) run.borderStyle = attrs.borderStyle;
458
469
  if (attrs.position !== void 0) run.position = attrs.position;
459
470
  if (attrs.layoutInCell !== void 0) run.layoutInCell = attrs.layoutInCell;
460
471
  if (trackedChange?.isInsertion) run.isInsertion = true;
@@ -858,6 +869,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
858
869
  const dtf = pmAttrs.defaultTextFormatting;
859
870
  if (dtf) {
860
871
  if (dtf.fontSize !== void 0) attrs.defaultFontSize = dtf.fontSize / 2;
872
+ if (attrs.listMarker !== void 0 && !attrs.listMarkerHidden && dtf.fontSizeCs !== void 0 && (dtf.fontSize === void 0 || dtf.fontSizeCs > dtf.fontSize)) attrs.listParagraphMarkFontSize = dtf.fontSizeCs / 2;
861
873
  if (dtf.fontFamily) {
862
874
  const resolvedFamily = resolveWesternThemeFont(dtf.fontFamily, theme);
863
875
  if (resolvedFamily) attrs.defaultFontFamily = resolvedFamily;
@@ -1214,6 +1226,9 @@ function convertImage(node, startPos, pageContentHeight) {
1214
1226
  if (attrs.cropRight != null) imgBlock.cropRight = attrs.cropRight;
1215
1227
  if (attrs.cropBottom != null) imgBlock.cropBottom = attrs.cropBottom;
1216
1228
  if (attrs.cropLeft != null) imgBlock.cropLeft = attrs.cropLeft;
1229
+ if (attrs.borderWidth != null) imgBlock.borderWidth = attrs.borderWidth;
1230
+ if (attrs.borderColor) imgBlock.borderColor = attrs.borderColor;
1231
+ if (attrs.borderStyle) imgBlock.borderStyle = attrs.borderStyle;
1217
1232
  return imgBlock;
1218
1233
  }
1219
1234
  /**
@@ -1,6 +1,7 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
2
  import { measuredLineAdvance } from "./lineFlow.js";
3
3
  import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
4
+ import { resolveFloatingTableX } from "./measure/floatingTablePosition.js";
4
5
  import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
5
6
  import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
6
7
  import { createPaginator } from "./paginator.js";
@@ -477,6 +478,16 @@ const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => block.rows[rowI
477
478
  const firstBlock = cell.blocks.at(0);
478
479
  return firstBlock?.kind === "paragraph" && firstBlock.attrs?.renderedPageBreakBefore === true;
479
480
  }) ?? false;
481
+ const flowBlockHasTrackedChanges = (block) => {
482
+ if (block.kind === "paragraph") return block.runs.some((run) => {
483
+ if (run.kind === "lineBreak") return false;
484
+ return run.isInsertion || run.isDeletion;
485
+ });
486
+ if (block.kind === "table") return block.rows.some((row) => row.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)));
487
+ if (block.kind === "textBox") return block.content.some(flowBlockHasTrackedChanges);
488
+ return false;
489
+ };
490
+ const tableRowHasTrackedChanges = (block, rowIndex) => block.rows[rowIndex]?.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)) ?? false;
480
491
  /**
481
492
  * Layout a table block onto pages.
482
493
  */
@@ -522,7 +533,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
522
533
  const rowState = paginator.getCurrentState();
523
534
  const rowHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, rowState) ? headerRowsHeight : 0;
524
535
  const rowAvailableHeight = paginator.getAvailableHeight() - rowHeaderOverhead - rowState.trailingSpacing;
525
- if (!(rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0) && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
536
+ if (!(rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0) && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && !tableRowHasTrackedChanges(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
526
537
  paginator.forcePageBreak();
527
538
  continue;
528
539
  }
@@ -657,7 +668,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
657
668
  }
658
669
  }
659
670
  function collectTableRowFootnoteIds(row, footnoteHeightById) {
660
- if (!row || !footnoteHeightById) return [];
671
+ if (!row || !footnoteHeightById || row.hidden) return [];
661
672
  const ids = [];
662
673
  const walk = (blocks) => {
663
674
  for (const block of blocks) {
@@ -666,7 +677,10 @@ function collectTableRowFootnoteIds(row, footnoteHeightById) {
666
677
  continue;
667
678
  }
668
679
  if (block.kind === "table") {
669
- for (const nestedRow of block.rows) for (const cell of nestedRow.cells) walk(cell.blocks);
680
+ for (const nestedRow of block.rows) {
681
+ if (nestedRow.hidden) continue;
682
+ for (const cell of nestedRow.cells) walk(cell.blocks);
683
+ }
670
684
  continue;
671
685
  }
672
686
  if (block.kind === "textBox") walk(block.content);
@@ -692,14 +706,7 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
692
706
  if (floating?.vertAnchor === "page") baseY = 0;
693
707
  else if (floating?.vertAnchor === "text") baseY = state.cursorY;
694
708
  let x = paginator.getColumnX(state.columnIndex);
695
- if (floating?.tblpX !== void 0) x = baseX + floating.tblpX;
696
- else if (floating?.tblpXSpec) {
697
- const spec = floating.tblpXSpec;
698
- if (spec === "left" || spec === "inside") x = baseX;
699
- else if (spec === "right" || spec === "outside") x = baseX + contentWidth - tableWidth;
700
- else x = baseX + (contentWidth - tableWidth) / 2;
701
- } else if (block.justification === "center") x = baseX + (contentWidth - tableWidth) / 2;
702
- else if (block.justification === "right") x = baseX + contentWidth - tableWidth;
709
+ if (floating) x = baseX + resolveFloatingTableX(floating, block.justification, tableWidth, contentWidth);
703
710
  let y = state.cursorY;
704
711
  let usedExplicitY = false;
705
712
  if (floating?.tblpY !== void 0) {
@@ -713,7 +720,7 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
713
720
  else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
714
721
  }
715
722
  if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
716
- const usesNumericOffset = floating?.tblpX !== void 0;
723
+ const usesNumericOffset = floating?.tblpX !== void 0 && floating.tblpXSpec === void 0;
717
724
  const clampToPage = floating?.horzAnchor === "page" || usesNumericOffset;
718
725
  const minX = clampToPage ? 0 : margins.left;
719
726
  const maxX = clampToPage ? page.size.w - tableWidth : margins.left + contentWidth - tableWidth;
@@ -1,3 +1,4 @@
1
+ import { clearFontResolvedCache } from "./measureHelpers.js";
1
2
  import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
2
3
  import { lineBreakPolicyCacheParts } from "./effectiveLineBreakPolicy.js";
3
4
  //#region src/layout-engine/measure/cache.ts
@@ -255,6 +256,7 @@ function clearAllCaches() {
255
256
  clearTextWidthCache();
256
257
  clearFontMetricsCache();
257
258
  clearParagraphMeasureCache();
259
+ clearFontResolvedCache();
258
260
  }
259
261
  /**
260
262
  * Get total size of all caches
@@ -12,8 +12,8 @@ const resolveEffectiveLineBreakPolicy = ({ attrs, run }) => {
12
12
  provider: {
13
13
  ...locale ? { locale } : {},
14
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 } : {},
15
+ ...before && languageMatches(before.language, locale) ? { noLineBreaksBefore: toCodePointSet(before.characters) } : {},
16
+ ...after && languageMatches(after.language, locale) ? { noLineBreaksAfter: toCodePointSet(after.characters) } : {},
17
17
  ...rules?.useLegacyEthiopicAmharicRules ? { useLegacyEthiopicAmharicRules: true } : {},
18
18
  ...automaticHyphenation?.doNotHyphenateCaps !== void 0 ? { doNotHyphenateCaps: automaticHyphenation.doNotHyphenateCaps } : {},
19
19
  ...run.allCaps === true ? { renderedAllCaps: true } : {}
@@ -56,5 +56,11 @@ const languageMatches = (ruleLanguage, locale) => {
56
56
  const active = locale.toLowerCase();
57
57
  return active === rule || active.startsWith(`${rule}-`) || rule.startsWith(`${active}-`);
58
58
  };
59
+ /**
60
+ * Split an already length-capped kinsoku character list (settingsParser.ts)
61
+ * into a code-point Set so line-edge membership checks are O(1) instead of
62
+ * an O(n) `String.includes` scan per character measured.
63
+ */
64
+ const toCodePointSet = (characters) => new Set(Array.from(characters));
59
65
  //#endregion
60
66
  export { lineBreakPolicyCacheParts, resolveEffectiveLineBreakPolicy };
@@ -0,0 +1,9 @@
1
+ import { FloatingTablePosition, TableBlock } from "../types.js";
2
+
3
+ //#region src/layout-engine/measure/floatingTablePosition.d.ts
4
+ /**
5
+ * X of the table's left edge relative to the content-box left edge, px.
6
+ */
7
+ declare function resolveFloatingTableX(anchor: FloatingTablePosition, justification: TableBlock["justification"], tableWidth: number, contentWidth: number): number;
8
+ //#endregion
9
+ export { resolveFloatingTableX };
@@ -0,0 +1,17 @@
1
+ //#region src/layout-engine/measure/floatingTablePosition.ts
2
+ /**
3
+ * X of the table's left edge relative to the content-box left edge, px.
4
+ */
5
+ function resolveFloatingTableX(anchor, justification, tableWidth, contentWidth) {
6
+ const clamp = (x) => Math.max(0, x);
7
+ const spec = anchor.tblpXSpec;
8
+ if (spec === "left" || spec === "inside") return 0;
9
+ if (spec === "right" || spec === "outside") return clamp(contentWidth - tableWidth);
10
+ if (spec === "center") return clamp((contentWidth - tableWidth) / 2);
11
+ if (anchor.tblpX !== void 0) return anchor.tblpX;
12
+ if (justification === "center") return clamp((contentWidth - tableWidth) / 2);
13
+ if (justification === "right") return clamp(contentWidth - tableWidth);
14
+ return 0;
15
+ }
16
+ //#endregion
17
+ export { resolveFloatingTableX };
@@ -7,4 +7,5 @@ import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
7
7
  import { MeasureParagraphOptions, getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
8
8
  import { clearAllCaches, clearFontMetricsCache, clearParagraphMeasureCache, clearTextWidthCache, getCachedFontMetrics, getCachedParagraphMeasure, getCachedTextWidth, getFontCacheSize, getParagraphCacheSize, getTextCacheSize, getTotalCacheSize, hashParagraphBlock, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFontCacheSize, setParagraphCacheSize, setTextCacheSize } from "./cache.js";
9
9
  import { FolioMeasurementFeatureFlags, isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
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 };
10
+ import { resolveFloatingTableX } from "./floatingTablePosition.js";
11
+ 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, resolveFloatingTableX, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
@@ -4,6 +4,7 @@ import { defaultLineBreakProvider, getLineBreakProvider, getLineBreakProviderGen
4
4
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
5
5
  import { getFloatingAvailableWidth, getFloatingMargins, rectsToFloatingZones } from "./floatingZones.js";
6
6
  import { getRunCharWidths, measureParagraph, measureParagraphs } from "./measureParagraph.js";
7
+ import { resolveFloatingTableX } from "./floatingTablePosition.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 { isWorkerFontMetricsEnabled, setFolioMeasurementFlags } from "./featureFlags.js";
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 };
10
+ 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, resolveFloatingTableX, setCachedFontMetrics, setCachedParagraphMeasure, setCachedTextWidth, setFolioMeasurementFlags, setFontCacheSize, setLineBreakProvider, setMeasureProvider, setParagraphCacheSize, setTextCacheSize, twipsToPx };
@@ -10,8 +10,8 @@
10
10
  type LineBreakPolicy = {
11
11
  /** BCP-47 language tag resolved from `w:lang` for this run. */locale?: string; /** Apply East Asian first/last-character restrictions (`w:kinsoku`). */
12
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. */
13
+ noLineBreaksBefore?: ReadonlySet<string>; /** Document replacement list: characters that may not end a line. */
14
+ noLineBreaksAfter?: ReadonlySet<string>; /** Use the legacy Ethiopic/Amharic compatibility behavior. */
15
15
  useLegacyEthiopicAmharicRules?: boolean; /** Keep words made entirely of capital letters unhyphenated. */
16
16
  doNotHyphenateCaps?: boolean; /** The run renders with the DOCX all-caps transform. */
17
17
  renderedAllCaps?: boolean;
@@ -128,9 +128,9 @@ const previousCodePoint = (text, index) => {
128
128
  if (trailing >= 56320 && trailing <= 57343 && index >= 2) return text.slice(index - 2, index);
129
129
  return text[index - 1];
130
130
  };
131
- const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.includes(character) : DEFAULT_PROHIBITED_LINE_START.has(character));
132
- const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.includes(character) : DEFAULT_PROHIBITED_LINE_END.has(character));
133
- 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);
131
+ const isProhibitedLineStart = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksBefore !== void 0 ? policy.noLineBreaksBefore.has(character) : DEFAULT_PROHIBITED_LINE_START.has(character));
132
+ const isProhibitedLineEnd = (character, policy) => character !== void 0 && policy?.kinsoku !== false && (policy?.noLineBreaksAfter !== void 0 ? policy.noLineBreaksAfter.has(character) : DEFAULT_PROHIBITED_LINE_END.has(character));
133
+ const isCjkLineBreakParticipant = (character, policy) => character !== void 0 && (CJK_SCRIPT.test(character) || policy?.kinsoku !== false && (DEFAULT_PROHIBITED_LINE_START.has(character) || DEFAULT_PROHIBITED_LINE_END.has(character) || policy?.noLineBreaksBefore?.has(character) === true || policy?.noLineBreaksAfter?.has(character) === true));
134
134
  const isLegacyEthiopicBreakCharacter = (character, policy) => {
135
135
  if (!policy?.useLegacyEthiopicAmharicRules || character === void 0) return false;
136
136
  const codePoint = character.codePointAt(0);
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
2
2
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
3
+ import { resolveFloatingTableX } from "./floatingTablePosition.js";
3
4
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
4
5
  import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
5
6
  import { layoutTextBoxContent } from "./textBoxParagraphLayout.js";
@@ -17,6 +18,14 @@ import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorder
17
18
  */
18
19
  const NO_WRAP_MEASURE_WIDTH = 1e6;
19
20
  /**
21
+ * Sanity cap on the table column count derived from summed cell colSpans
22
+ * when a table has no `tblGrid`/explicit column widths. Mirrors the
23
+ * `MAX_TABLE_COLUMNS` clamp applied to `w:gridSpan` at parse time
24
+ * (`docx/tableParser.ts`); enforced again here since a hostile colSpan sum
25
+ * would otherwise size the fallback column array and cell grid unbounded.
26
+ */
27
+ const MAX_TABLE_COLUMNS = 63;
28
+ /**
20
29
  * Check if an image run is a *text-wrapping* floating image — it
21
30
  * occupies an exclusion zone the body text should flow around.
22
31
  *
@@ -59,7 +68,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
59
68
  let columnWidths = tableBlock.columnWidths ?? [];
60
69
  const explicitWidthPx = resolveTableWidthPx(tableBlock.width, tableBlock.widthType, contentWidth);
61
70
  if (columnWidths.length === 0 && tableBlock.rows.length > 0) {
62
- const colCount = tableBlock.rows[0].cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0);
71
+ const colCount = Math.min(tableBlock.rows[0].cells.reduce((sum, cell) => sum + (cell.colSpan ?? 1), 0), MAX_TABLE_COLUMNS);
63
72
  const equalWidth = (explicitWidthPx ?? contentWidth) / Math.max(1, colCount);
64
73
  columnWidths = Array.from({ length: colCount }, () => equalWidth);
65
74
  } else if (columnWidths.length > 0 && explicitWidthPx) {
@@ -285,13 +294,7 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
285
294
  const distBottom = floating.bottomFromText ?? 0;
286
295
  let leftMargin = 0;
287
296
  let rightMargin = 0;
288
- let x = 0;
289
- if (floating.tblpX !== void 0) x = floating.tblpX;
290
- else if (floating.tblpXSpec) if (floating.tblpXSpec === "left" || floating.tblpXSpec === "inside") x = 0;
291
- else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x = defaultContentWidth - tableWidth;
292
- else x = (defaultContentWidth - tableWidth) / 2;
293
- else if (tableBlock.justification === "center") x = (defaultContentWidth - tableWidth) / 2;
294
- else if (tableBlock.justification === "right") x = defaultContentWidth - tableWidth;
297
+ const x = resolveFloatingTableX(floating, tableBlock.justification, tableWidth, defaultContentWidth);
295
298
  if (x < defaultContentWidth / 2) leftMargin = x + tableWidth + distRight;
296
299
  else rightMargin = defaultContentWidth - x + distLeft;
297
300
  const topY = floating.tblpY ?? 0;
@@ -22,6 +22,15 @@ type ResolvedFontCache = {
22
22
  * Get the resolved font data for a font family, with caching.
23
23
  */
24
24
  declare function getResolvedData(fontFamily: string): ResolvedFontCache;
25
+ /**
26
+ * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
27
+ * given key can change even when the raw font names don't — e.g. when the
28
+ * active document's embedded-font family map changes (see
29
+ * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
30
+ * cleared whenever that happens, not just on font-name changes. Folded into
31
+ * `clearAllCaches` (`./cache.ts`) so callers get it for free.
32
+ */
33
+ declare function clearFontResolvedCache(): void;
25
34
  /**
26
35
  * Build a CSS font string from styling properties
27
36
  *
@@ -79,4 +88,4 @@ declare function halfPtToPx(halfPt: number): number;
79
88
  */
80
89
  declare function pxToHalfPt(px: number): number;
81
90
  //#endregion
82
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
91
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -62,6 +62,17 @@ function getResolvedFallback(fontFamily) {
62
62
  return getResolvedData(fontFamily).cssFallback;
63
63
  }
64
64
  /**
65
+ * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
66
+ * given key can change even when the raw font names don't — e.g. when the
67
+ * active document's embedded-font family map changes (see
68
+ * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
69
+ * cleared whenever that happens, not just on font-name changes. Folded into
70
+ * `clearAllCaches` (`./cache.ts`) so callers get it for free.
71
+ */
72
+ function clearFontResolvedCache() {
73
+ fontResolvedCache.clear();
74
+ }
75
+ /**
65
76
  * Build a CSS font string from styling properties
66
77
  *
67
78
  * Font sizes are in points and need to be converted to pixels for canvas.
@@ -156,4 +167,4 @@ function pxToHalfPt(px) {
156
167
  return pxToPt(px) * 2;
157
168
  }
158
169
  //#endregion
159
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
170
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -515,12 +515,20 @@ function computeProtectedCrossRunGlueWidths(block) {
515
515
  }
516
516
  return widths;
517
517
  }
518
+ /**
519
+ * Cap on the number of runs `collectCrossRunWord` traverses per call. A
520
+ * paragraph split into many tiny (including empty) runs would otherwise let
521
+ * the search walk the full run list for every word-start position it is
522
+ * invoked from, without the per-word text-length budget ever kicking in.
523
+ */
524
+ const MAX_CROSS_RUN_SEGMENTS = 64;
518
525
  /** Collect one lexical word continued through adjacent formatting runs. */
519
526
  function collectCrossRunWord({ block, startRunIndex, startChar }) {
520
527
  const segments = [];
521
528
  let text = "";
522
529
  let width = 0;
523
530
  for (let runIndex = startRunIndex; runIndex < block.runs.length; runIndex++) {
531
+ if (runIndex - startRunIndex >= MAX_CROSS_RUN_SEGMENTS) return;
524
532
  const run = block.runs[runIndex];
525
533
  if (!run || !isTextRun(run)) break;
526
534
  const start = runIndex === startRunIndex ? startChar : 0;
@@ -1138,6 +1146,14 @@ function measureParagraph(block, maxWidth, options) {
1138
1146
  }
1139
1147
  }
1140
1148
  }
1149
+ const listParagraphMarkFontSize = attrs?.listParagraphMarkFontSize;
1150
+ if (listParagraphMarkFontSize !== void 0 && listParagraphMarkFontSize > currentLine.maxFontSize) {
1151
+ const fontFamily = currentLine.maxFontMetrics?.fontFamily ?? attrs?.defaultFontFamily;
1152
+ updateMaxFont({
1153
+ fontSize: listParagraphMarkFontSize,
1154
+ ...fontFamily === void 0 ? {} : { fontFamily }
1155
+ });
1156
+ }
1141
1157
  finalizeLine();
1142
1158
  let totalWithSpacing = lines.reduce((sum, line) => sum + measuredLineAdvance(line), 0);
1143
1159
  if (spacing?.before) totalWithSpacing += spacing.before;