@stll/folio-core 0.59.0 → 0.61.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 (226) hide show
  1. package/README.md +23 -1
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +57 -63
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +4 -2
  13. package/dist/compat/eigenpal.js +2 -2
  14. package/dist/controller/canonicalClipboard.js +2 -1
  15. package/dist/controller/canonicalListRendering.d.ts +4 -6
  16. package/dist/controller/canonicalListRendering.js +3 -157
  17. package/dist/controller/canonicalSession.js +3 -2
  18. package/dist/controller/canonicalStructure.js +10 -7
  19. package/dist/controller/fontReadiness.js +1 -1
  20. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  21. package/dist/controller/headerFooterEditorManager.js +11 -0
  22. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  23. package/dist/controller/hiddenEditorManager.js +3 -1
  24. package/dist/controller/layoutPipeline.d.ts +3 -1
  25. package/dist/controller/layoutPipeline.js +19 -4
  26. package/dist/controller/layoutSession.d.ts +4 -1
  27. package/dist/display-list/build/fontTable.js +7 -5
  28. package/dist/display-list/build/furniture.d.ts +1 -0
  29. package/dist/display-list/build/furniture.js +1 -0
  30. package/dist/display-list/build/imagePrimitives.js +3 -6
  31. package/dist/display-list/build/watermarkPrimitives.d.ts +2 -1
  32. package/dist/display-list/build/watermarkPrimitives.js +9 -3
  33. package/dist/display-list/editor/pageRenderer.js +1 -0
  34. package/dist/docx/bookmarkIdNormalization.d.ts +10 -0
  35. package/dist/docx/bookmarkIdNormalization.js +100 -0
  36. package/dist/docx/bookmarkParser.js +4 -2
  37. package/dist/docx/canonicalStoryRepack.js +3 -2
  38. package/dist/docx/commentTextParagraph.d.ts +6 -0
  39. package/dist/docx/commentTextParagraph.js +15 -0
  40. package/dist/docx/conformance.js +6 -1
  41. package/dist/docx/diagramPreview.js +2 -1
  42. package/dist/docx/documentParser.d.ts +13 -1
  43. package/dist/docx/documentParser.js +1 -1
  44. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  45. package/dist/docx/encryption/compoundFile.js +2 -1
  46. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  47. package/dist/docx/encryption/cryptoBytes.js +3 -18
  48. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  49. package/dist/docx/encryption/encryptionInfo.js +2 -1
  50. package/dist/docx/ensureParaIds.js +2 -1
  51. package/dist/docx/groupDrawingParser.js +6 -1
  52. package/dist/docx/importedIdCollisions.d.ts +136 -0
  53. package/dist/docx/importedIdCollisions.js +215 -0
  54. package/dist/docx/listModelNormalization.d.ts +10 -0
  55. package/dist/docx/listModelNormalization.js +166 -0
  56. package/dist/docx/metadataPrivacy.js +3 -2
  57. package/dist/docx/metafileRaster.js +2 -1
  58. package/dist/docx/numberingParser.js +2 -2
  59. package/dist/docx/packageParts.js +6 -5
  60. package/dist/docx/paragraphParser.js +6 -4
  61. package/dist/docx/paragraphPropertySource.js +1 -2
  62. package/dist/docx/parseContext.d.ts +2 -1
  63. package/dist/docx/parseContext.js +1 -0
  64. package/dist/docx/parseWarningMessage.js +1 -0
  65. package/dist/docx/parser.js +10 -5
  66. package/dist/docx/preservedRunContent.js +11 -2
  67. package/dist/docx/relsParser.js +1 -1
  68. package/dist/docx/removeHeaderFooterParts.js +8 -7
  69. package/dist/docx/replyToComment.js +3 -13
  70. package/dist/docx/rezip.js +90 -90
  71. package/dist/docx/runParser.js +12 -6
  72. package/dist/docx/runPropertyPresence.d.ts +11 -0
  73. package/dist/docx/runPropertyPresence.js +14 -0
  74. package/dist/docx/selectedXmlIdentityScan.d.ts +10 -0
  75. package/dist/docx/selectedXmlIdentityScan.js +36 -0
  76. package/dist/docx/selectiveSave.js +19 -17
  77. package/dist/docx/selectiveXmlPatch.js +44 -52
  78. package/dist/docx/serializer/commentSerializer.d.ts +3 -1
  79. package/dist/docx/serializer/commentSerializer.js +11 -2
  80. package/dist/docx/serializer/runSerializer.js +5 -4
  81. package/dist/docx/serializer/tableSerializer.js +2 -1
  82. package/dist/docx/serializer/trackedChangeAttributes.d.ts +1 -3
  83. package/dist/docx/serializer/trackedChangeAttributes.js +1 -11
  84. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  85. package/dist/docx/server/boundedArchive.js +2 -1
  86. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  87. package/dist/docx/server/inspectDocxPackage.js +5 -1
  88. package/dist/docx/server/validateDocxConformance.js +2 -1
  89. package/dist/docx/tableParser.js +7 -4
  90. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  91. package/dist/docx/tableRowPropertyPresence.js +15 -0
  92. package/dist/docx/unzip.js +2 -1
  93. package/dist/docx/xmlEncoding.js +6 -5
  94. package/dist/fonts/embeddedFontFingerprint.d.ts +5 -0
  95. package/dist/fonts/embeddedFontFingerprint.js +18 -0
  96. package/dist/fonts/embeddedFonts.d.ts +1 -1
  97. package/dist/fonts/embeddedFonts.js +1 -1
  98. package/dist/fonts/fontsourceFaces.js +2 -1
  99. package/dist/fonts/headlessMeasure.js +0 -0
  100. package/dist/fonts/sfnt/parse.js +6 -9
  101. package/dist/fonts/sfnt/subset.js +2 -1
  102. package/dist/fonts/sfnt/tables.js +2 -3
  103. package/dist/fonts/sfnt/woff.js +2 -1
  104. package/dist/headless-layout.js +1 -0
  105. package/dist/index.d.ts +4 -2
  106. package/dist/index.js +2 -2
  107. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  108. package/dist/internal/revisionResolutionInline.js +22 -1
  109. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  110. package/dist/layout-bridge/convert/flowConversionShared.d.ts +4 -0
  111. package/dist/layout-bridge/convert/footnoteLayout.d.ts +5 -1
  112. package/dist/layout-bridge/convert/footnoteLayout.js +16 -3
  113. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -0
  114. package/dist/layout-bridge/convert/headerFooterLayout.js +13 -14
  115. package/dist/layout-bridge/convert/paragraphConversion.js +8 -1
  116. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  117. package/dist/layout-bridge/convert/toFlowBlocks.js +25 -1
  118. package/dist/layout-engine/measure/cache.js +39 -7
  119. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  120. package/dist/layout-engine/measure/lineTypography.js +1 -0
  121. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  122. package/dist/layout-engine/measure/listMarkerWidth.js +1 -0
  123. package/dist/layout-engine/measure/measureContainer.js +3 -4
  124. package/dist/layout-engine/measure/measureHelpers.d.ts +6 -9
  125. package/dist/layout-engine/measure/measureHelpers.js +21 -18
  126. package/dist/layout-engine/measure/measureParagraph.js +2 -0
  127. package/dist/layout-engine/measure/measureTypes.d.ts +2 -0
  128. package/dist/layout-engine/types.d.ts +4 -1
  129. package/dist/layout-painter/renderPage.d.ts +1 -0
  130. package/dist/layout-painter/renderPage.js +11 -3
  131. package/dist/layout-painter/renderParagraph.js +13 -3
  132. package/dist/layout-painter/renderWatermark.d.ts +1 -0
  133. package/dist/layout-painter/renderWatermark.js +7 -3
  134. package/dist/managers/AutoSaveManager.js +2 -1
  135. package/dist/managers/EditorModeManager.d.ts +10 -1
  136. package/dist/managers/EditorModeManager.js +15 -1
  137. package/dist/managers/TableSelectionManager.d.ts +1 -1
  138. package/dist/managers/autoSaveCodec.js +2 -6
  139. package/dist/managers/editorShortcuts.d.ts +2 -1
  140. package/dist/managers/editorShortcuts.js +2 -1
  141. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  142. package/dist/managers/reviewDisplayMode.js +15 -0
  143. package/dist/paged-layout/pageText.d.ts +11 -2
  144. package/dist/paged-layout/pageText.js +8 -3
  145. package/dist/pdf/fonts.js +5 -14
  146. package/dist/pdf/images.js +3 -2
  147. package/dist/pdf/objects.d.ts +0 -11
  148. package/dist/pdf/objects.js +7 -9
  149. package/dist/pdf/writePdf.js +2 -1
  150. package/dist/prosemirror/canonicalSelectionRange.d.ts +4 -1
  151. package/dist/prosemirror/canonicalSelectionRange.js +9 -3
  152. package/dist/prosemirror/commands/comments.js +51 -13
  153. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  154. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  155. package/dist/prosemirror/conversion/fromProseDoc.js +9 -4
  156. package/dist/prosemirror/conversion/toProseDoc.js +4 -4
  157. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +8 -2
  158. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +39 -3
  159. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.d.ts +8 -2
  160. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +4 -3
  161. package/dist/prosemirror/extensions/features/ImageDragExtension.js +4 -3
  162. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +4 -3
  163. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +9 -3
  164. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +6 -5
  165. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +8 -2
  166. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +11 -3
  167. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +4 -3
  168. package/dist/prosemirror/extensions/marks/markUtils.js +1 -1
  169. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  170. package/dist/prosemirror/findReplaceSelection.d.ts +11 -1
  171. package/dist/prosemirror/findReplaceSelection.js +15 -1
  172. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  173. package/dist/prosemirror/markupViewProjection.js +21 -3
  174. package/dist/prosemirror/pluginKeyOwner.d.ts +13 -0
  175. package/dist/prosemirror/pluginKeyOwner.js +35 -0
  176. package/dist/prosemirror/plugins/aiCitationDecorations.d.ts +11 -4
  177. package/dist/prosemirror/plugins/aiCitationDecorations.js +16 -3
  178. package/dist/prosemirror/plugins/aiSuggestionDecorations.d.ts +11 -4
  179. package/dist/prosemirror/plugins/aiSuggestionDecorations.js +18 -3
  180. package/dist/prosemirror/plugins/anonymizationDecorations.d.ts +11 -11
  181. package/dist/prosemirror/plugins/anonymizationDecorations.js +23 -13
  182. package/dist/prosemirror/plugins/autocompleteSuggestion.d.ts +8 -2
  183. package/dist/prosemirror/plugins/autocompleteSuggestion.js +27 -5
  184. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +8 -2
  185. package/dist/prosemirror/plugins/contentControlWidgets.js +4 -3
  186. package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +6 -5
  187. package/dist/prosemirror/plugins/createDocScanPlugin.js +1 -1
  188. package/dist/prosemirror/plugins/documentNumbering.js +5 -3
  189. package/dist/prosemirror/plugins/documentStyleState.d.ts +8 -2
  190. package/dist/prosemirror/plugins/documentStyleState.js +2 -2
  191. package/dist/prosemirror/plugins/documentStyles.js +1 -1
  192. package/dist/prosemirror/plugins/selectionTracker.d.ts +8 -2
  193. package/dist/prosemirror/plugins/selectionTracker.js +4 -3
  194. package/dist/prosemirror/plugins/suggestionMode.d.ts +8 -2
  195. package/dist/prosemirror/plugins/suggestionMode.js +4 -3
  196. package/dist/prosemirror/plugins/templateDirectives.d.ts +8 -2
  197. package/dist/prosemirror/plugins/templateDirectives.js +19 -4
  198. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +8 -2
  199. package/dist/prosemirror/plugins/templatePreviewValues.js +27 -5
  200. package/dist/prosemirror/plugins/templateSlashMenu.d.ts +8 -2
  201. package/dist/prosemirror/plugins/templateSlashMenu.js +23 -5
  202. package/dist/prosemirror/plugins/transactionInvariants.js +29 -4
  203. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  204. package/dist/prosemirror/replacedAnnotations.js +71 -12
  205. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  206. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  207. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  208. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  209. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  210. package/dist/utils/base64.d.ts +1 -31
  211. package/dist/utils/base64.js +19 -33
  212. package/dist/utils/clipboard.js +5 -4
  213. package/dist/utils/colorResolver.js +11 -2
  214. package/dist/utils/findReplace.d.ts +5 -0
  215. package/dist/utils/fontLoader.d.ts +2 -2
  216. package/dist/utils/fontLoader.js +2 -7
  217. package/dist/utils/fontResolver.d.ts +7 -3
  218. package/dist/utils/fontResolver.js +6 -8
  219. package/dist/utils/replaceText.js +21 -4
  220. package/dist/utils/sliceText.d.ts +2 -0
  221. package/dist/utils/sliceText.js +2 -0
  222. package/dist/utils/textEncoding.d.ts +2 -0
  223. package/dist/utils/textEncoding.js +2 -0
  224. package/dist/utils/tiffConverter.d.ts +0 -9
  225. package/dist/utils/tiffConverter.js +2 -4
  226. package/package.json +73 -31
@@ -1,7 +1,9 @@
1
1
  import { cloneParagraphWithPropertySource } from "../../docx/paragraphPropertySource.js";
2
2
  import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../../layout-engine/types.js";
3
3
  import { headerFooterToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
4
+ import { projectStoryMarkupView } from "../../prosemirror/markupViewProjection.js";
4
5
  import { emuToPixels } from "../../utils/units.js";
6
+ import { remapMarkupViewBlocks } from "./markupViewFlow.js";
5
7
  import { toFlowBlocks } from "./toFlowBlocks.js";
6
8
  //#region src/layout-bridge/convert/headerFooterLayout.ts
7
9
  const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
@@ -341,18 +343,7 @@ function convertHeaderFooterToContent(headerFooter, contentWidth, metrics, optio
341
343
  if (options.styles) proseDocOptions.styles = options.styles;
342
344
  if (options.theme !== void 0) proseDocOptions.theme = options.theme;
343
345
  if (options.numbering !== void 0) proseDocOptions.numbering = options.numbering;
344
- const pmDoc = headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions);
345
- const flowOptions = {};
346
- if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
347
- if (options.styles) flowOptions.styles = options.styles;
348
- if (options.theme !== void 0) flowOptions.theme = options.theme;
349
- if (options.fontAlternates !== void 0) flowOptions.fontAlternates = options.fontAlternates;
350
- if (options.defaultTabStopTwips !== void 0) flowOptions.defaultTabStopTwips = options.defaultTabStopTwips;
351
- if (options.lineBreakRules) flowOptions.lineBreakRules = options.lineBreakRules;
352
- if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
353
- if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
354
- if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
355
- return finalizeHeaderFooterContent(toFlowBlocks(pmDoc, flowOptions), contentWidth, metrics, options);
346
+ return convertHeaderFooterPmDocToContent(headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions), contentWidth, metrics, options);
356
347
  }
357
348
  /**
358
349
  * Same as {@link convertHeaderFooterToContent}, but sourced from a live
@@ -367,17 +358,25 @@ function convertHeaderFooterToContent(headerFooter, contentWidth, metrics, optio
367
358
  */
368
359
  function convertHeaderFooterPmDocToContent(pmDoc, contentWidth, metrics, options) {
369
360
  if (!pmDoc || pmDoc.content.size === 0) return;
370
- const flowOptions = {};
361
+ const flowOptions = { showCommentHighlights: options.showCommentHighlights !== false && options.markupView !== "no-markup" && options.markupView !== "original" };
371
362
  if (options.defaultSize !== void 0) flowOptions.defaultSize = options.defaultSize;
372
363
  if (options.styles) flowOptions.styles = options.styles;
373
364
  if (options.theme !== void 0) flowOptions.theme = options.theme;
365
+ if (options.embeddedFontFamilyMap !== void 0) flowOptions.embeddedFontFamilyMap = options.embeddedFontFamilyMap;
374
366
  if (options.fontAlternates !== void 0) flowOptions.fontAlternates = options.fontAlternates;
375
367
  if (options.defaultTabStopTwips !== void 0) flowOptions.defaultTabStopTwips = options.defaultTabStopTwips;
376
368
  if (options.lineBreakRules) flowOptions.lineBreakRules = options.lineBreakRules;
377
369
  if (options.justificationCompatibility) flowOptions.justificationCompatibility = options.justificationCompatibility;
378
370
  if (options.tableIndentCompatibility) flowOptions.tableIndentCompatibility = options.tableIndentCompatibility;
379
371
  if (options.automaticHyphenation) flowOptions.automaticHyphenation = options.automaticHyphenation;
380
- return finalizeHeaderFooterContent(toFlowBlocks(pmDoc, flowOptions), contentWidth, metrics, options);
372
+ const projection = projectStoryMarkupView({
373
+ doc: pmDoc,
374
+ markupView: options.markupView ?? "all-markup",
375
+ styles: options.styles,
376
+ numbering: options.numbering
377
+ });
378
+ const flowBlocks = toFlowBlocks(projection.type === "resolved" ? projection.doc : pmDoc, flowOptions);
379
+ return finalizeHeaderFooterContent(projection.type === "resolved" ? remapMarkupViewBlocks(flowBlocks, projection) : flowBlocks, contentWidth, metrics, options);
381
380
  }
382
381
  function horizontalImageLeft({ run, contentWidth, metrics }) {
383
382
  const horizontal = run.position?.horizontal;
@@ -1,6 +1,7 @@
1
1
  import { getFontAlternate } from "../../fonts/fontAlternates.js";
2
2
  import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
3
3
  import { expectParagraphAttrs } from "../../prosemirror/attrs/index.js";
4
+ import { expectBookmarkBoundaryAttrs } from "../../prosemirror/bookmarkBoundaryAttrs.js";
4
5
  import { expectTextBoxAnchorAttrs } from "../../prosemirror/textBoxAnchorAttrs.js";
5
6
  import { nextBlockId, twipsToPixels } from "./flowConversionShared.js";
6
7
  import { convertParagraphAttrs } from "./paragraphAttrs.js";
@@ -76,7 +77,13 @@ function convertParagraph(node, startPos, options, pageBreaks) {
76
77
  if (isFullyHiddenParagraph) attrs.suppressEmptyParagraphHeight = true;
77
78
  if (runs.length === 0 && expectDetachedWatermarkHostAttr(node.attrs)) attrs.suppressEmptyParagraphHeight = false;
78
79
  if (runs.length === 0 && pmAttrs._pageBreakCarrier === true && !hasVisibleParagraphPayload(attrs)) attrs.suppressEmptyParagraphHeight = true;
79
- const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name);
80
+ const bookmarkNames = pmAttrs.bookmarks?.map((b) => b.name) ?? [];
81
+ node.descendants((child) => {
82
+ if (child.type.name !== "bookmarkBoundary") return true;
83
+ const marker = expectBookmarkBoundaryAttrs(child);
84
+ if (marker.type === "start") bookmarkNames.push(marker.name);
85
+ return false;
86
+ });
80
87
  const block = {
81
88
  kind: "paragraph",
82
89
  id: nextBlockId(),
@@ -58,7 +58,8 @@ function paragraphToRuns(node, startPos, _options, pageBreaks) {
58
58
  });
59
59
  return;
60
60
  }
61
- const effectiveMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
61
+ const projectedMarks = mergeProjectedInlineMarks(inheritedMarks, child.marks);
62
+ const effectiveMarks = _options.showCommentHighlights === false ? projectedMarks.filter((mark) => mark.type.name !== "comment") : projectedMarks;
62
63
  if (child.type.name === "pageBreakRun") {
63
64
  const trackedChange = extractRunFormatting(effectiveMarks, theme, fontAlternates);
64
65
  if (trackedChange.isDeletion) {
@@ -412,10 +412,34 @@ function toFlowBlocks(doc, options = {}) {
412
412
  }
413
413
  const mergedBlocks = mergeRunInParagraphs(blocks);
414
414
  const tableCellLinePitch = doc.attrs["_adjustLineHeightInTable"] === true ? "sectionGrid" : void 0;
415
- return groupParagraphFrames(coalesceTrailingPageBreakBeforeContinuousSection(applySectionStartsToBoundaries(applySectionDocumentGrid(mergedBlocks, {
415
+ const result = groupParagraphFrames(coalesceTrailingPageBreakBeforeContinuousSection(applySectionStartsToBoundaries(applySectionDocumentGrid(mergedBlocks, {
416
416
  finalLinePitchTwips: opts.finalSectionDocumentGridLinePitchTwips,
417
417
  tableCellLinePitch
418
418
  }), readFinalSectionStart(doc)), options.splitPageBreakAndParagraphMark === true, sectionBreaksWithoutMarker), nextBlockId);
419
+ if (options.embeddedFontFamilyMap) attachEmbeddedFontFamilies(result, options.embeddedFontFamilyMap);
420
+ return result;
419
421
  }
422
+ /** Attach editor-owned resolution to freshly derived blocks without changing authored data. */
423
+ const attachEmbeddedFontFamilies = (blocks, familyMap) => {
424
+ for (const block of blocks) switch (block.kind) {
425
+ case "paragraph":
426
+ for (const run of block.runs) if (run.kind !== "image" && run.kind !== "lineBreak") run.embeddedFontFamilyMap = familyMap;
427
+ block.attrs ??= {};
428
+ block.attrs.embeddedFontFamilyMap = familyMap;
429
+ if (block.attrs.listMarkerFormatting) block.attrs.listMarkerFormatting.embeddedFontFamilyMap = familyMap;
430
+ break;
431
+ case "table":
432
+ for (const row of block.rows) for (const cell of row.cells) attachEmbeddedFontFamilies(cell.blocks, familyMap);
433
+ break;
434
+ case "textBox":
435
+ attachEmbeddedFontFamilies(block.content, familyMap);
436
+ break;
437
+ case "image":
438
+ case "sectionBreak":
439
+ case "pageBreak":
440
+ case "columnBreak": break;
441
+ default: return block;
442
+ }
443
+ };
420
444
  //#endregion
421
445
  export { WORD_UNSPECIFIED_FONT_SIZE, collectNoteRefs, convertBorderSpecToLayout, formatCounter, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };
@@ -1,7 +1,14 @@
1
+ import { embeddedFontFingerprint } from "../../fonts/embeddedFontFingerprint.js";
1
2
  import { getLineBreakProviderGeneration } from "./lineBreakProvider.js";
2
3
  import { clearFontResolvedCache } from "./measureHelpers.js";
3
4
  //#region src/layout-engine/measure/cache.ts
4
5
  /**
6
+ * Measurement Cache
7
+ *
8
+ * LRU cache for text width measurements and paragraph layout results.
9
+ * Improves performance by avoiding repeated measurements of identical content.
10
+ */
11
+ /**
5
12
  * Default max entries for text width cache
6
13
  * Large documents (30+ pages) can generate 20,000+ unique text measurements.
7
14
  * A generous default avoids cache thrashing on big docs.
@@ -176,21 +183,31 @@ const imageMeasureCacheInput = (run) => ({
176
183
  exactLineHeight: run.exactLineHeight,
177
184
  positioned: run.position !== void 0
178
185
  });
179
- const runMeasureCacheInput = (run) => {
186
+ const runMeasureCacheInput = (run, fontContextRef) => {
180
187
  switch (run.kind) {
181
188
  case "text":
182
189
  case "tab":
183
- case "lineBreak":
184
190
  case "renderedPageBreak":
185
191
  case "field":
186
- case "math": return run;
192
+ case "math": {
193
+ const { embeddedFontFamilyMap, ...formatting } = run;
194
+ return {
195
+ ...formatting,
196
+ embeddedFontFamilies: fontContextRef(embeddedFontFamilyMap)
197
+ };
198
+ }
199
+ case "lineBreak": return run;
187
200
  case "image": return imageMeasureCacheInput(run);
188
201
  default: return run;
189
202
  }
190
203
  };
191
- const paragraphAttrsMeasureCacheInput = (attrs) => {
204
+ const paragraphAttrsMeasureCacheInput = (attrs, fontContextRef) => {
192
205
  if (attrs === void 0) return void 0;
193
- const { snapToGrid, ...measurementAttrs } = attrs;
206
+ const { snapToGrid, embeddedFontFamilyMap, ...rest } = attrs;
207
+ const measurementAttrs = {
208
+ ...rest,
209
+ embeddedFontFamilies: fontContextRef(embeddedFontFamilyMap)
210
+ };
194
211
  if (measurementAttrs.documentGridLinePitch === void 0) return measurementAttrs;
195
212
  return {
196
213
  ...measurementAttrs,
@@ -199,10 +216,25 @@ const paragraphAttrsMeasureCacheInput = (attrs) => {
199
216
  };
200
217
  /** Serialize the complete, semantically normalized paragraph measurement contract into a cache key. */
201
218
  function hashParagraphBlock(block) {
219
+ const fontContexts = [];
220
+ const contextRefs = /* @__PURE__ */ new Map();
221
+ const fontContextRef = (families) => {
222
+ if (!families) return void 0;
223
+ const fingerprint = embeddedFontFingerprint(families);
224
+ const existing = contextRefs.get(fingerprint);
225
+ if (existing !== void 0) return existing;
226
+ const index = fontContexts.length;
227
+ contextRefs.set(fingerprint, index);
228
+ fontContexts.push(fingerprint);
229
+ return index;
230
+ };
231
+ const attrs = paragraphAttrsMeasureCacheInput(block.attrs, fontContextRef);
232
+ const runs = block.runs.map((run) => runMeasureCacheInput(run, fontContextRef));
202
233
  return JSON.stringify({
203
234
  lineBreakProviderGeneration: getLineBreakProviderGeneration(),
204
- attrs: paragraphAttrsMeasureCacheInput(block.attrs),
205
- runs: block.runs.map(runMeasureCacheInput)
235
+ attrs,
236
+ runs,
237
+ fontContexts
206
238
  });
207
239
  }
208
240
  /**
@@ -1,3 +1,4 @@
1
+ import { sliceText } from "../../utils/sliceText.js";
1
2
  import { splitTrailingToken } from "../../utils/trailingText.js";
2
3
  import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
3
4
  import "./lineBreakProvider.js";
@@ -47,7 +48,11 @@ function computeFollowingTextLeads(block) {
47
48
  leads[index] = lead;
48
49
  const run = block.runs[index];
49
50
  if (!run || !isTextRun(run)) lead = void 0;
50
- else if (run.text) lead = run.text[0];
51
+ else if (run.text) lead = sliceText({
52
+ text: run.text,
53
+ units: "codePoints",
54
+ end: 1
55
+ });
51
56
  }
52
57
  return leads;
53
58
  }
@@ -101,6 +106,7 @@ function computeProtectedCrossRunGlueWidths(block) {
101
106
  * invoked from, without the per-word text-length budget ever kicking in.
102
107
  */
103
108
  const MAX_CROSS_RUN_SEGMENTS = 64;
109
+ const MAX_SCALAR_CODE_UNITS = 2;
104
110
  /** Collect one lexical word continued through adjacent formatting runs. */
105
111
  function collectCrossRunWord({ block, startRunIndex, startChar }) {
106
112
  const segments = [];
@@ -115,7 +121,11 @@ function collectCrossRunWord({ block, startRunIndex, startChar }) {
115
121
  if (remainder.length === 0) continue;
116
122
  if (runIndex !== startRunIndex && isSpaceOrTab(remainder[0])) break;
117
123
  const remainingBudget = 256 - text.length;
118
- const boundedRemainder = remainder.slice(0, remainingBudget + 1);
124
+ const boundedRemainder = sliceText({
125
+ text: remainder,
126
+ units: "codeUnits",
127
+ end: remainingBudget + MAX_SCALAR_CODE_UNITS
128
+ });
119
129
  const firstBreak = findWordBreaks(boundedRemainder, resolveEffectiveLineBreakPolicy({
120
130
  attrs: block.attrs,
121
131
  run
@@ -96,6 +96,7 @@ const WORD_SINGLE_LINE_FLOOR = 1.15;
96
96
  */
97
97
  function calculateEmptyParagraphMetrics(fontSize, spacing, fontFamily, attrs) {
98
98
  const result = calculateTypographyMetrics(fontSize, spacing, getFontMetrics({
99
+ ...attrs?.embeddedFontFamilyMap ? { embeddedFontFamilyMap: attrs.embeddedFontFamilyMap } : {},
99
100
  fontSize,
100
101
  fontFamily: fontFamily ?? "Times New Roman",
101
102
  ...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
@@ -13,6 +13,7 @@ declare const DEFAULT_TAB_STOP_TWIPS = 720;
13
13
  * of the paragraph do not format its number.
14
14
  */
15
15
  declare function resolveListMarkerFont(block: ParagraphBlock): {
16
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
16
17
  fontFamily: string;
17
18
  alternateFontFamily?: string;
18
19
  fontSize: number;
@@ -27,6 +27,7 @@ function resolveListMarkerFont(block) {
27
27
  alternateFontFamily = markerFormatting.alternateFontFamily;
28
28
  }
29
29
  const base = {
30
+ ...attrs?.embeddedFontFamilyMap ? { embeddedFontFamilyMap: attrs.embeddedFontFamilyMap } : {},
30
31
  fontFamily,
31
32
  ...alternateFontFamily !== void 0 ? { alternateFontFamily } : {},
32
33
  fontSize: markerFormatting?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE,
@@ -61,8 +61,7 @@ function canvasGetFontMetrics(style) {
61
61
  const bold = style.bold ?? false;
62
62
  const italic = style.italic ?? false;
63
63
  const fontVariant = style.fontVariant;
64
- const alternateFontFamily = style.alternateFontFamily;
65
- const fontCacheFamily = alternateFontFamily ? `${fontFamily}\u0000${alternateFontFamily}` : fontFamily;
64
+ const fontCacheFamily = getResolvedData(fontFamily, style).cssFallback;
66
65
  const cached = getCachedFontMetrics(fontCacheFamily, fontSize, bold, italic, fontVariant);
67
66
  if (cached !== void 0) return {
68
67
  fontSize,
@@ -98,7 +97,7 @@ function canvasGetFontMetrics(style) {
98
97
  }
99
98
  } catch {}
100
99
  lineHeight = Math.max(lineHeight, ascent + descent);
101
- const singleLineRatio = getResolvedData(fontFamily, alternateFontFamily).singleLineRatio;
100
+ const singleLineRatio = getResolvedData(fontFamily, style).singleLineRatio;
102
101
  const result = {
103
102
  fontSize,
104
103
  ascent,
@@ -165,7 +164,7 @@ function canvasMeasureTextWidth(text, sourceStyle) {
165
164
  * takes the single-font path (no double-counting, no recursion).
166
165
  */
167
166
  function glyphAdvanceStyle(style) {
168
- const result = {};
167
+ const result = { ...style.embeddedFontFamilyMap ? { embeddedFontFamilyMap: style.embeddedFontFamilyMap } : {} };
169
168
  if (style.fontFamily !== void 0) result.fontFamily = style.fontFamily;
170
169
  if (style.alternateFontFamily !== void 0) result.alternateFontFamily = style.alternateFontFamily;
171
170
  if (style.fontSize !== void 0) result.fontSize = style.fontSize;
@@ -33,20 +33,17 @@ type ResolvedFontCache = {
33
33
  /**
34
34
  * Get the resolved font data for a font family, with caching.
35
35
  */
36
- declare function getResolvedData(fontFamily: string, alternateFontFamily?: string): ResolvedFontCache;
36
+ type ResolvedDataOptions = {
37
+ alternateFontFamily?: string | undefined;
38
+ embeddedFontFamilyMap?: ReadonlyMap<string, string> | undefined;
39
+ };
40
+ declare function getResolvedData(fontFamily: string, options?: ResolvedDataOptions): ResolvedFontCache;
37
41
  /**
38
42
  * Whether a face of `family` (a `FontFace.family`, possibly quoted) can change
39
43
  * a width this page has measured.
40
44
  */
41
45
  declare function isLayoutFontFamily(family: string): boolean;
42
- /**
43
- * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
44
- * given key can change even when the raw font names don't — e.g. when the
45
- * active document's embedded-font family map changes (see
46
- * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
47
- * cleared whenever that happens, not just on font-name changes. Folded into
48
- * `clearAllCaches` (`./cache.ts`) so callers get it for free.
49
- */
46
+ /** Clear cached resolution after a browser font-set or font-policy change. */
50
47
  declare function clearFontResolvedCache(): void;
51
48
  /**
52
49
  * Build a CSS font string from styling properties
@@ -59,6 +59,7 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
59
59
  const horizontalScale = normalizeHorizontalScalePercent(run.horizontalScale);
60
60
  return {
61
61
  fontFamily: run.fontFamily ?? fallbackFontFamily,
62
+ ...run.embeddedFontFamilyMap ? { embeddedFontFamilyMap: run.embeddedFontFamilyMap } : {},
62
63
  ...run.alternateFontFamily !== void 0 ? { alternateFontFamily: run.alternateFontFamily } : {},
63
64
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
64
65
  ...run.eastAsiaAlternateFontFamily !== void 0 ? { eastAsiaAlternateFontFamily: run.eastAsiaAlternateFontFamily } : {},
@@ -80,19 +81,27 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
80
81
  }
81
82
  /** Cache for resolved font data */
82
83
  const fontResolvedCache = /* @__PURE__ */ new Map();
83
- /**
84
- * Get the resolved font data for a font family, with caching.
85
- */
86
- function getResolvedData(fontFamily, alternateFontFamily) {
84
+ let embeddedFontResolvedCaches = /* @__PURE__ */ new WeakMap();
85
+ function getResolvedData(fontFamily, options = {}) {
86
+ const { alternateFontFamily, embeddedFontFamilyMap } = options;
87
+ let cache = fontResolvedCache;
88
+ if (embeddedFontFamilyMap) {
89
+ const owned = embeddedFontResolvedCaches.get(embeddedFontFamilyMap);
90
+ cache = owned ?? /* @__PURE__ */ new Map();
91
+ if (!owned) embeddedFontResolvedCaches.set(embeddedFontFamilyMap, cache);
92
+ }
87
93
  const cacheKey = `${fontFamily}\u0000${alternateFontFamily ?? ""}`;
88
- let cached = fontResolvedCache.get(cacheKey);
94
+ let cached = cache.get(cacheKey);
89
95
  if (cached === void 0) {
90
- const resolved = resolveFontFamily(fontFamily, alternateFontFamily);
96
+ const resolved = resolveFontFamily(fontFamily, {
97
+ alternateFontName: alternateFontFamily,
98
+ embeddedFontFamilyMap
99
+ });
91
100
  cached = {
92
101
  cssFallback: resolved.cssFallback,
93
102
  singleLineRatio: resolved.singleLineRatio
94
103
  };
95
- fontResolvedCache.set(cacheKey, cached);
104
+ cache.set(cacheKey, cached);
96
105
  for (const family of parseFontFamilyList(resolved.cssFallback)) layoutFontFamilies.add(normalizeFontFamilyName(family));
97
106
  }
98
107
  return cached;
@@ -119,19 +128,13 @@ function isLayoutFontFamily(family) {
119
128
  /**
120
129
  * Get the CSS fallback string for a font family, with caching.
121
130
  */
122
- function getResolvedFallback(fontFamily, alternateFontFamily) {
123
- return getResolvedData(fontFamily, alternateFontFamily).cssFallback;
131
+ function getResolvedFallback(fontFamily, style) {
132
+ return getResolvedData(fontFamily, style).cssFallback;
124
133
  }
125
- /**
126
- * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
127
- * given key can change even when the raw font names don't — e.g. when the
128
- * active document's embedded-font family map changes (see
129
- * `utils/fontResolver.ts`'s `setEmbeddedFontFamilyMap`) — so this must be
130
- * cleared whenever that happens, not just on font-name changes. Folded into
131
- * `clearAllCaches` (`./cache.ts`) so callers get it for free.
132
- */
134
+ /** Clear cached resolution after a browser font-set or font-policy change. */
133
135
  function clearFontResolvedCache() {
134
136
  fontResolvedCache.clear();
137
+ embeddedFontResolvedCaches = /* @__PURE__ */ new WeakMap();
135
138
  }
136
139
  /**
137
140
  * Build a CSS font string from styling properties
@@ -154,7 +157,7 @@ function buildFontString(style) {
154
157
  const fontSizePx = ptToPx(style.fontSize ?? 11);
155
158
  parts.push(`${fontSizePx}px`);
156
159
  const fontFamily = style.fontFamily ?? "Times New Roman";
157
- parts.push(getResolvedFallback(fontFamily, style.alternateFontFamily));
160
+ parts.push(getResolvedFallback(fontFamily, style));
158
161
  return parts.join(" ");
159
162
  }
160
163
  /**
@@ -169,6 +169,7 @@ function measureParagraph(block, maxWidth, options) {
169
169
  const metrics = lineFontMetrics ?? getFontMetrics({
170
170
  fontSize: paragraphFontSize,
171
171
  fontFamily: paragraphFontFamily,
172
+ ...attrs?.embeddedFontFamilyMap ? { embeddedFontFamilyMap: attrs.embeddedFontFamilyMap } : {},
172
173
  ...attrs?.defaultAlternateFontFamily !== void 0 ? { alternateFontFamily: attrs.defaultAlternateFontFamily } : {}
173
174
  });
174
175
  const finalTypography = { ...calculateTypographyMetrics(fontSize, spacing, metrics) };
@@ -402,6 +403,7 @@ function measureParagraph(block, maxWidth, options) {
402
403
  if (isMathRun(run)) {
403
404
  const style = {
404
405
  fontFamily: run.fontFamily ?? "Cambria Math",
406
+ ...run.embeddedFontFamilyMap ? { embeddedFontFamilyMap: run.embeddedFontFamilyMap } : {},
405
407
  fontSize: run.fontSize ?? 11,
406
408
  ...run.bold !== void 0 ? { bold: run.bold } : {},
407
409
  ...run.italic !== void 0 ? { italic: run.italic } : {}
@@ -13,6 +13,8 @@ declare namespace measureTypes_d_exports {
13
13
  * Font styling properties for measurement
14
14
  */
15
15
  type FontStyle = {
16
+ /** Embedded families owned by this editor, never ambient process state. */
17
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
16
18
  fontFamily?: string;
17
19
  alternateFontFamily?: string;
18
20
  /**
@@ -13,6 +13,8 @@ type BlockId = string | number;
13
13
  * Common run formatting properties applied to text runs.
14
14
  */
15
15
  type RunFormatting = {
16
+ /** Embedded families owned by this editor, never ambient process state. */
17
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
16
18
  bold?: boolean;
17
19
  italic?: boolean;
18
20
  /**
@@ -160,7 +162,7 @@ type RunFormatting = {
160
162
  /** Groups a suggestion's runs for accept/reject and scroll-to. */
161
163
  suggestionId?: string;
162
164
  };
163
- type ListMarkerFormatting = Pick<RunFormatting, "fontFamily" | "alternateFontFamily" | "eastAsiaFontFamily" | "eastAsiaAlternateFontFamily" | "complexScriptFontFamily" | "complexScriptAlternateFontFamily" | "fontSize" | "complexScriptFontSize" | "bold" | "complexScriptBold" | "italic" | "complexScriptItalic" | "rtl" | "forceComplexScript" | "color">;
165
+ type ListMarkerFormatting = Pick<RunFormatting, "embeddedFontFamilyMap" | "fontFamily" | "alternateFontFamily" | "eastAsiaFontFamily" | "eastAsiaAlternateFontFamily" | "complexScriptFontFamily" | "complexScriptAlternateFontFamily" | "fontSize" | "complexScriptFontSize" | "bold" | "complexScriptBold" | "italic" | "complexScriptItalic" | "rtl" | "forceComplexScript" | "color">;
164
166
  /**
165
167
  * Hyperlink information for a run.
166
168
  */
@@ -416,6 +418,7 @@ declare const isListNumPr: (value: ParagraphNumberingOverride | null | undefined
416
418
  * Paragraph block attributes.
417
419
  */
418
420
  type ParagraphAttrs = {
421
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
419
422
  alignment?: "left" | "center" | "right" | "justify";
420
423
  /** Document-generation policy for justified line fitting. */
421
424
  justificationCompatibility?: {
@@ -93,6 +93,7 @@ type PaintPageRequest = {
93
93
  readonly options: RenderPageOptions;
94
94
  };
95
95
  type RenderPageOptions = {
96
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
96
97
  /** Document to create elements in (default: window.document) */
97
98
  document?: Document;
98
99
  /** Bookmark name -> 1-indexed page, for resolving PAGEREF fields at paint. */
@@ -1,3 +1,4 @@
1
+ import { embeddedFontFingerprint } from "../fonts/embeddedFontFingerprint.js";
1
2
  import "../layout-engine/measure/floatingLineClearance.js";
2
3
  import { resolveFloatingTableX } from "../layout-engine/measure/floatingTablePosition.js";
3
4
  import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
@@ -59,7 +60,7 @@ function applyPageStyles(element, width, height, options) {
59
60
  element.style.border = "none";
60
61
  element.style.boxShadow = "none";
61
62
  element.style.outline = "none";
62
- element.style.fontFamily = getDefaultPageFontFamily();
63
+ element.style.fontFamily = resolveFontFamily(DEFAULT_FONT_FAMILY, options).cssFallback;
63
64
  element.style.fontSize = `${1056 / 72}px`;
64
65
  element.style.color = "var(--doc-canvas-text, #000)";
65
66
  }
@@ -179,7 +180,10 @@ function syncPageWatermarkOverlay(pageEl, page, options, doc) {
179
180
  for (const stale of Array.from(pageEl.querySelectorAll(":scope > .layout-page-watermark"))) stale.remove();
180
181
  const watermark = options.watermark;
181
182
  if (!watermark) return;
182
- const overlay = renderWatermarkLayer(watermark, page, doc, options.watermarkImageSrc !== void 0 ? { imageSrc: options.watermarkImageSrc } : {});
183
+ const overlay = renderWatermarkLayer(watermark, page, doc, {
184
+ ...options.watermarkImageSrc !== void 0 ? { imageSrc: options.watermarkImageSrc } : {},
185
+ ...options.embeddedFontFamilyMap ? { embeddedFontFamilyMap: options.embeddedFontFamilyMap } : {}
186
+ });
183
187
  if (!overlay) return;
184
188
  const contentEl = pageEl.querySelector(`:scope > .${PAGE_CLASS_NAMES.content}`);
185
189
  if (contentEl) {
@@ -785,7 +789,10 @@ function renderPage(page, context, options = {}) {
785
789
  const pageBorderEl = renderPageBorderOverlay(page, options, doc);
786
790
  if (pageBorderEl && options.pageBorders?.zOrder === "back") pageEl.append(pageBorderEl);
787
791
  if (options.watermark) {
788
- const watermarkEl = renderWatermarkLayer(options.watermark, page, doc, options.watermarkImageSrc !== void 0 ? { imageSrc: options.watermarkImageSrc } : {});
792
+ const watermarkEl = renderWatermarkLayer(options.watermark, page, doc, {
793
+ ...options.watermarkImageSrc !== void 0 ? { imageSrc: options.watermarkImageSrc } : {},
794
+ ...options.embeddedFontFamilyMap ? { embeddedFontFamilyMap: options.embeddedFontFamilyMap } : {}
795
+ });
789
796
  if (watermarkEl) pageEl.append(watermarkEl);
790
797
  }
791
798
  const contentEl = doc.createElement("div");
@@ -1403,6 +1410,7 @@ function headerFooterContentFingerprint(prefix, entries) {
1403
1410
  */
1404
1411
  function computeOptionsHash(options) {
1405
1412
  const parts = [];
1413
+ if (options.embeddedFontFamilyMap) parts.push(`embedded-fonts:${embeddedFontFingerprint(options.embeddedFontFamilyMap)}`);
1406
1414
  if (options.headerContent) parts.push(`hdr:${options.headerContent.blocks.length},${options.headerContent.height},${options.headerContent.visualTop ?? 0},${options.headerContent.visualBottom ?? options.headerContent.height},${options.headerContent.rId ?? ""},${options.headerContent.textSig ?? ""}`);
1407
1415
  if (options.footerContent) parts.push(`ftr:${options.footerContent.blocks.length},${options.footerContent.height},${options.footerContent.visualTop ?? 0},${options.footerContent.visualBottom ?? options.footerContent.height},${options.footerContent.rId ?? ""},${options.footerContent.textSig ?? ""}`);
1408
1416
  parts.push(...headerFooterContentFingerprint("hdr-map", options.headerContentByRId));
@@ -190,7 +190,10 @@ const UNICODE_BIDI_BY_WRAPPER_CONTROL = {
190
190
  * Apply text run styles to an element
191
191
  */
192
192
  function applyRunStyles(element, run) {
193
- if (run.fontFamily) element.style.fontFamily = resolveFontFamily(run.fontFamily, run.alternateFontFamily).cssFallback;
193
+ if (run.fontFamily) element.style.fontFamily = resolveFontFamily(run.fontFamily, {
194
+ alternateFontName: run.alternateFontFamily,
195
+ embeddedFontFamilyMap: run.embeddedFontFamilyMap
196
+ }).cssFallback;
194
197
  if (run.fontSize) element.style.fontSize = `${fontSizePtToPx(run.fontSize)}px`;
195
198
  if (run.bold) element.style.fontWeight = "700";
196
199
  if (run.italic) element.style.fontStyle = "italic";
@@ -1043,6 +1046,7 @@ function runVisualSpaceContraction({ text, horizontalScale, visualSpaceContracti
1043
1046
  */
1044
1047
  function runMeasureStyle(run) {
1045
1048
  return {
1049
+ ...run.embeddedFontFamilyMap ? { embeddedFontFamilyMap: run.embeddedFontFamilyMap } : {},
1046
1050
  ...run.bold !== void 0 ? { bold: run.bold } : {},
1047
1051
  ...run.italic !== void 0 ? { italic: run.italic } : {},
1048
1052
  ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
@@ -1168,7 +1172,10 @@ function createTextMeasurer(doc) {
1168
1172
  if (style.smallCaps) fontPrefixParts.push("small-caps");
1169
1173
  if (bold) fontPrefixParts.push("700");
1170
1174
  fontPrefixParts.push(`${size * 96 / 72}px`);
1171
- return `${fontPrefixParts.join(" ")} ${resolveFontFamily(family, alternateFamily).cssFallback}`;
1175
+ return `${fontPrefixParts.join(" ")} ${resolveFontFamily(family, {
1176
+ alternateFontName: alternateFamily,
1177
+ embeddedFontFamilyMap: style.embeddedFontFamilyMap
1178
+ }).cssFallback}`;
1172
1179
  };
1173
1180
  const baseFont = fontString(fontFamily, fontSize, style.bold, style.italic, style.alternateFontFamily);
1174
1181
  const complex = resolveComplexScriptFormatting(style);
@@ -1670,7 +1677,10 @@ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting,
1670
1677
  const span = doc.createElement("span");
1671
1678
  span.className = "layout-list-marker";
1672
1679
  span.style.display = "inline-block";
1673
- span.style.fontFamily = resolveFontFamily(formatting.fontFamily, formatting.alternateFontFamily).cssFallback;
1680
+ span.style.fontFamily = resolveFontFamily(formatting.fontFamily, {
1681
+ alternateFontName: formatting.alternateFontFamily,
1682
+ embeddedFontFamilyMap: formatting.embeddedFontFamilyMap
1683
+ }).cssFallback;
1674
1684
  if (formatting.fontSize) span.style.fontSize = `${formatting.fontSize * 96 / 72}px`;
1675
1685
  if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
1676
1686
  if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
@@ -2,6 +2,7 @@ import { document_d_exports } from "../types/document.js";
2
2
  import { Page } from "../layout-engine/types.js";
3
3
  //#region src/layout-painter/renderWatermark.d.ts
4
4
  type RenderWatermarkOptions = {
5
+ embeddedFontFamilyMap?: ReadonlyMap<string, string>;
5
6
  /** Resolved image src for picture watermarks (`data:` / `blob:` only). */
6
7
  imageSrc?: string;
7
8
  };
@@ -25,7 +25,11 @@ function renderWatermarkLayer(watermark, page, doc, options = {}) {
25
25
  layer.style.display = "flex";
26
26
  layer.style.alignItems = "center";
27
27
  layer.style.justifyContent = "center";
28
- if (watermark.kind === "text") layer.append(renderTextWatermark(watermark, doc));
28
+ if (watermark.kind === "text") layer.append(renderTextWatermark({
29
+ watermark,
30
+ doc,
31
+ options
32
+ }));
29
33
  else {
30
34
  const imageSrc = options.imageSrc;
31
35
  if (!imageSrc) return null;
@@ -33,10 +37,10 @@ function renderWatermarkLayer(watermark, page, doc, options = {}) {
33
37
  }
34
38
  return layer;
35
39
  }
36
- function renderTextWatermark(watermark, doc) {
40
+ function renderTextWatermark({ watermark, doc, options }) {
37
41
  const el = doc.createElement("div");
38
42
  el.textContent = watermark.text;
39
- el.style.fontFamily = resolveFontFamily(watermark.font ?? "Calibri").cssFallback;
43
+ el.style.fontFamily = resolveFontFamily(watermark.font ?? "Calibri", options).cssFallback;
40
44
  el.style.fontSize = "144px";
41
45
  el.style.fontWeight = "bold";
42
46
  el.style.whiteSpace = "nowrap";
@@ -1,3 +1,4 @@
1
+ import { encodeUtf8 } from "../utils/textEncoding.js";
1
2
  import { Subscribable } from "./Subscribable.js";
2
3
  import { decodeAutoSaveEnvelope, encodeAutoSaveDocument, encodeAutoSaveEnvelopeFromDocument } from "./autoSaveCodec.js";
3
4
  //#region src/managers/AutoSaveManager.ts
@@ -228,7 +229,7 @@ function getAutoSaveStorageSize(storageKey = DEFAULT_STORAGE_KEY) {
228
229
  try {
229
230
  const data = localStorage.getItem(storageKey);
230
231
  if (!data) return 0;
231
- return new Blob([data]).size;
232
+ return encodeUtf8(data).byteLength;
232
233
  } catch {
233
234
  return 0;
234
235
  }
@@ -5,6 +5,15 @@ type EditorMode = "editing" | "suggesting" | "viewing";
5
5
  /** How tracked changes render. Drives the display mode dropdown. */
6
6
  declare const DISPLAY_MODES: readonly ["all-markup", "simple-markup", "no-markup", "original"];
7
7
  type DisplayMode = (typeof DISPLAY_MODES)[number];
8
+ /** Why a user action cannot edit the displayed document. */
9
+ type EditorReadOnlyReason = "host-read-only" | "viewing-mode" | "review-display-mode";
10
+ declare const getReviewViewReadOnlyReason: (displayMode: DisplayMode) => EditorReadOnlyReason | null;
11
+ type EditorReadOnlyReasonOptions = {
12
+ editingMode: EditorMode;
13
+ readOnlyProp: boolean;
14
+ displayMode: DisplayMode;
15
+ };
16
+ declare const getEditorReadOnlyReason: ({ editingMode, readOnlyProp, displayMode }: EditorReadOnlyReasonOptions) => EditorReadOnlyReason | null;
8
17
  /** EditorModeManager snapshot. */
9
18
  type EditorModeSnapshot = {
10
19
  /** Uncontrolled internal editing mode (the host's controlled prop wins over this). */
@@ -26,4 +35,4 @@ declare class EditorModeManager extends Subscribable<EditorModeSnapshot> {
26
35
  setDisplayMode(mode: DisplayMode): void;
27
36
  }
28
37
  //#endregion
29
- export { DISPLAY_MODES, DisplayMode, EditorMode, EditorModeManager, EditorModeSnapshot, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };
38
+ export { DISPLAY_MODES, DisplayMode, EditorMode, EditorModeManager, EditorModeSnapshot, EditorReadOnlyReason, getEditorReadOnlyReason, getReviewViewReadOnlyReason, isReadOnlyMode, isTrackChangesMode, toggledTrackChangesMode };