@stll/folio-core 0.32.1 → 0.33.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 (222) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
  2. package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
  3. package/dist/ai-edits/apply.d.ts +49 -3
  4. package/dist/ai-edits/apply.js +692 -75
  5. package/dist/ai-edits/blockRange.d.ts +7 -3
  6. package/dist/ai-edits/blockRange.js +14 -6
  7. package/dist/ai-edits/headless.d.ts +39 -5
  8. package/dist/ai-edits/headless.js +76 -17
  9. package/dist/ai-edits/index.d.ts +6 -6
  10. package/dist/ai-edits/index.js +3 -3
  11. package/dist/ai-edits/read.d.ts +4 -0
  12. package/dist/ai-edits/read.js +30 -6
  13. package/dist/ai-edits/scoped-reading.js +4 -3
  14. package/dist/ai-edits/snapshot.d.ts +42 -2
  15. package/dist/ai-edits/snapshot.js +127 -62
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
  17. package/dist/ai-edits/table-row-column-mutations.js +98 -6
  18. package/dist/ai-edits/table-targets.d.ts +12 -3
  19. package/dist/ai-edits/table-targets.js +15 -5
  20. package/dist/ai-edits/types.d.ts +235 -7
  21. package/dist/ai-edits/word-diff.d.ts +54 -11
  22. package/dist/ai-edits/word-diff.js +221 -61
  23. package/dist/compare/__fixtures__/body-sequence.d.ts +33 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +67 -0
  25. package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
  26. package/dist/compare/__fixtures__/nested-table.js +50 -0
  27. package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
  28. package/dist/compare/__fixtures__/numbered-list.js +85 -0
  29. package/dist/compare/column-alignment.d.ts +27 -0
  30. package/dist/compare/column-alignment.js +162 -0
  31. package/dist/compare/compare.d.ts +107 -0
  32. package/dist/compare/compare.js +406 -0
  33. package/dist/compare/formatting.d.ts +29 -0
  34. package/dist/compare/formatting.js +84 -0
  35. package/dist/compare/plan.d.ts +22 -0
  36. package/dist/compare/plan.js +1059 -0
  37. package/dist/compare/reproducible-package.d.ts +17 -0
  38. package/dist/compare/reproducible-package.js +30 -0
  39. package/dist/compare/scenario.d.ts +77 -0
  40. package/dist/compare/scenario.js +263 -0
  41. package/dist/compare/types.d.ts +282 -0
  42. package/dist/compare/types.js +32 -0
  43. package/dist/compare/verification.d.ts +55 -0
  44. package/dist/compare/verification.js +146 -0
  45. package/dist/compat/eigenpal.d.ts +9 -6
  46. package/dist/compat/eigenpal.js +6 -3
  47. package/dist/controller/layoutPipeline.d.ts +13 -0
  48. package/dist/controller/layoutPipeline.js +15 -1
  49. package/dist/display-list/build/buildContext.d.ts +39 -0
  50. package/dist/display-list/build/buildContext.js +31 -0
  51. package/dist/display-list/build/buildDisplayList.d.ts +79 -0
  52. package/dist/display-list/build/buildDisplayList.js +283 -0
  53. package/dist/display-list/build/colors.d.ts +20 -0
  54. package/dist/display-list/build/colors.js +136 -0
  55. package/dist/display-list/build/floatingImages.d.ts +24 -0
  56. package/dist/display-list/build/floatingImages.js +41 -0
  57. package/dist/display-list/build/fontTable.d.ts +31 -0
  58. package/dist/display-list/build/fontTable.js +97 -0
  59. package/dist/display-list/build/furniture.d.ts +86 -0
  60. package/dist/display-list/build/furniture.js +98 -0
  61. package/dist/display-list/build/glyphs.d.ts +55 -0
  62. package/dist/display-list/build/glyphs.js +103 -0
  63. package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
  64. package/dist/display-list/build/headerFooterPrimitives.js +40 -0
  65. package/dist/display-list/build/imagePrimitives.d.ts +35 -0
  66. package/dist/display-list/build/imagePrimitives.js +264 -0
  67. package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
  68. package/dist/display-list/build/pageBorderPrimitives.js +111 -0
  69. package/dist/display-list/build/pageFurniture.d.ts +35 -0
  70. package/dist/display-list/build/pageFurniture.js +141 -0
  71. package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
  72. package/dist/display-list/build/paragraphPrimitives.js +819 -0
  73. package/dist/display-list/build/regions.d.ts +46 -0
  74. package/dist/display-list/build/regions.js +56 -0
  75. package/dist/display-list/build/storyPrimitives.d.ts +29 -0
  76. package/dist/display-list/build/storyPrimitives.js +279 -0
  77. package/dist/display-list/build/strokes.d.ts +19 -0
  78. package/dist/display-list/build/strokes.js +78 -0
  79. package/dist/display-list/build/tablePrimitives.d.ts +38 -0
  80. package/dist/display-list/build/tablePrimitives.js +409 -0
  81. package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
  82. package/dist/display-list/build/textBoxPrimitives.js +129 -0
  83. package/dist/display-list/build/textDecorations.d.ts +29 -0
  84. package/dist/display-list/build/textDecorations.js +29 -0
  85. package/dist/display-list/build/unsupported.d.ts +57 -0
  86. package/dist/display-list/build/unsupported.js +0 -0
  87. package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
  88. package/dist/display-list/build/watermarkPrimitives.js +128 -0
  89. package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
  90. package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
  91. package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
  92. package/dist/display-list/editor/displayListPagePainter.js +63 -0
  93. package/dist/display-list/editor/pageRenderer.d.ts +24 -0
  94. package/dist/display-list/editor/pageRenderer.js +36 -0
  95. package/dist/display-list/primitives.d.ts +95 -0
  96. package/dist/display-list/primitives.js +127 -0
  97. package/dist/display-list/types.d.ts +472 -0
  98. package/dist/display-list/types.js +0 -0
  99. package/dist/document-operations.d.ts +42 -9
  100. package/dist/document-operations.js +171 -16
  101. package/dist/document-stories.d.ts +16 -0
  102. package/dist/document-stories.js +48 -4
  103. package/dist/docx/packageParts.d.ts +35 -0
  104. package/dist/docx/packageParts.js +120 -0
  105. package/dist/docx/paragraphParser.js +22 -22
  106. package/dist/docx/paragraphTraversal.js +1 -1
  107. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  108. package/dist/docx/revisionIdNormalization.d.ts +13 -0
  109. package/dist/docx/revisionIdNormalization.js +103 -0
  110. package/dist/docx/rezip.js +26 -17
  111. package/dist/docx/selectiveSave.js +2 -2
  112. package/dist/docx/serializer/commentSerializer.js +6 -1
  113. package/dist/docx/serializer/paragraphSerializer.js +33 -4
  114. package/dist/docx/serializer/runSerializer.js +11 -10
  115. package/dist/docx/serializer/tableSerializer.js +2 -1
  116. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  117. package/dist/docx/server/createBilingualDocx.js +3 -2
  118. package/dist/docx/streamingXmlParser.d.ts +13 -1
  119. package/dist/docx/streamingXmlParser.js +49 -11
  120. package/dist/docx/unzip.d.ts +1 -2
  121. package/dist/docx/unzip.js +25 -6
  122. package/dist/docx/xmlParser.d.ts +3 -1
  123. package/dist/docx/xmlParser.js +33 -27
  124. package/dist/export-pdf.d.ts +43 -0
  125. package/dist/export-pdf.js +98 -0
  126. package/dist/fonts/headlessMeasure.d.ts +76 -0
  127. package/dist/fonts/headlessMeasure.js +0 -0
  128. package/dist/fonts/sfnt/parse.d.ts +51 -0
  129. package/dist/fonts/sfnt/parse.js +525 -0
  130. package/dist/fonts/sfnt/subset.d.ts +20 -0
  131. package/dist/fonts/sfnt/subset.js +350 -0
  132. package/dist/fonts/sfnt/tables.d.ts +88 -0
  133. package/dist/fonts/sfnt/tables.js +131 -0
  134. package/dist/fonts/sfnt/woff.d.ts +14 -0
  135. package/dist/fonts/sfnt/woff.js +163 -0
  136. package/dist/generated/text_shaper.js +324 -0
  137. package/dist/generated/text_shaper_bg.wasm +0 -0
  138. package/dist/headless-layout.d.ts +75 -0
  139. package/dist/headless-layout.js +350 -0
  140. package/dist/index.d.ts +9 -6
  141. package/dist/index.js +6 -3
  142. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  143. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  144. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  145. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  146. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  147. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  148. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  149. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  150. package/dist/layout-engine/measure/cache.d.ts +2 -0
  151. package/dist/layout-engine/measure/measureContainer.js +13 -0
  152. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  153. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  154. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  155. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  156. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  157. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  158. package/dist/layout-engine/types.d.ts +8 -0
  159. package/dist/layout-painter/renderPage.d.ts +29 -1
  160. package/dist/layout-painter/renderPage.js +47 -19
  161. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  162. package/dist/layout-painter/renderParagraph.js +1 -1
  163. package/dist/layout-painter/renderTable.js +37 -4
  164. package/dist/managers/editorShortcuts.d.ts +33 -1
  165. package/dist/managers/editorShortcuts.js +20 -1
  166. package/dist/markdown/renderRuns.js +7 -2
  167. package/dist/markdown/renderTable.js +5 -1
  168. package/dist/model.d.ts +3 -3
  169. package/dist/model.js +2 -2
  170. package/dist/pdf/contentStream.d.ts +73 -0
  171. package/dist/pdf/contentStream.js +143 -0
  172. package/dist/pdf/fonts.d.ts +96 -0
  173. package/dist/pdf/fonts.js +593 -0
  174. package/dist/pdf/images.d.ts +28 -0
  175. package/dist/pdf/images.js +439 -0
  176. package/dist/pdf/objects.d.ts +93 -0
  177. package/dist/pdf/objects.js +237 -0
  178. package/dist/pdf/pageSpace.d.ts +43 -0
  179. package/dist/pdf/pageSpace.js +57 -0
  180. package/dist/pdf/paint.d.ts +32 -0
  181. package/dist/pdf/paint.js +442 -0
  182. package/dist/pdf/writePdf.d.ts +56 -0
  183. package/dist/pdf/writePdf.js +345 -0
  184. package/dist/prosemirror/commands/comments.js +91 -6
  185. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  186. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  187. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  188. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  189. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  190. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  191. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  192. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  193. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  194. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  195. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  196. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  198. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  199. package/dist/prosemirror/revisionCarriers.js +19 -2
  200. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  201. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  202. package/dist/prosemirror/validation.js +55 -27
  203. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  204. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  205. package/dist/redline.js +12 -79
  206. package/dist/server.d.ts +4 -4
  207. package/dist/server.js +2 -2
  208. package/dist/shaping/placeRun.d.ts +51 -0
  209. package/dist/shaping/placeRun.js +84 -0
  210. package/dist/shaping/shaper.d.ts +92 -0
  211. package/dist/shaping/shaper.js +102 -0
  212. package/dist/types/block-id.d.ts +18 -2
  213. package/dist/types/block-id.js +49 -5
  214. package/dist/types/content.d.ts +2 -2
  215. package/dist/utils/clipboard.js +4 -3
  216. package/dist/utils/fontResolver.js +183 -0
  217. package/dist/utils/htmlComments.d.ts +17 -0
  218. package/dist/utils/htmlComments.js +22 -0
  219. package/dist/utils/scriptSegments.d.ts +12 -1
  220. package/dist/utils/scriptSegments.js +17 -1
  221. package/dist/version-comparison.d.ts +1 -1
  222. package/package.json +7 -3
@@ -25,63 +25,91 @@ const validateProseMirrorDocument = (doc) => {
25
25
  issues
26
26
  };
27
27
  };
28
+ /**
29
+ * Locate a bookmark boundary by the paragraph holding it, so a failure names a
30
+ * place a reader can find rather than only a node path.
31
+ *
32
+ * The paragraph's text is deliberately left out. Validation messages travel
33
+ * into logs and error reports, and document text is not log data.
34
+ */
35
+ const describeParagraph = (attrs, path) => {
36
+ const paraId = attrs?.ok === true ? attrs.value.paraId : void 0;
37
+ return paraId !== void 0 && paraId.length > 0 ? `paragraph ${paraId}` : `paragraph at ${path}`;
38
+ };
39
+ const describeBookmark = (id, name) => name === null || name.length === 0 ? `Bookmark id ${id}` : `Bookmark "${name}" (id ${id})`;
28
40
  const validateBookmarkBoundaryStructure = (doc, issues) => {
29
41
  const open = /* @__PURE__ */ new Map();
30
- const startedIds = /* @__PURE__ */ new Set();
31
- const registerStart = (id, path) => {
32
- if (startedIds.has(id)) {
42
+ const startedBy = /* @__PURE__ */ new Map();
43
+ /** Whether the boundary opened, and so needs a matching end registered later. */
44
+ const registerStart = (boundary) => {
45
+ const { id, name, path, paragraph, origin } = boundary;
46
+ const previousOrigin = startedBy.get(id);
47
+ if (previousOrigin !== void 0) {
48
+ if (origin === "attr" && previousOrigin === "attr") return false;
33
49
  issues.push({
34
50
  path,
35
- message: `Bookmark id ${id} has more than one start boundary.`
51
+ message: `${describeBookmark(id, name)} has more than one start boundary (${paragraph}).`
36
52
  });
37
- return;
53
+ return false;
38
54
  }
39
- startedIds.add(id);
40
- open.set(id, {
41
- id,
42
- path
43
- });
55
+ startedBy.set(id, origin);
56
+ open.set(id, boundary);
57
+ return true;
44
58
  };
45
- const registerEnd = (id, path) => {
46
- if (!open.get(id)) {
59
+ const registerEnd = (id, path, paragraph) => {
60
+ if (!open.has(id)) {
47
61
  issues.push({
48
62
  path,
49
- message: `Bookmark id ${id} has no open start boundary.`
63
+ message: `${describeBookmark(id, null)} has no open start boundary (${paragraph}).`
50
64
  });
51
65
  return;
52
66
  }
53
67
  open.delete(id);
54
68
  };
55
- const visit = (node, path) => {
69
+ const visit = (node, path, paragraph) => {
56
70
  const paragraphAttrs = node.type.name === "paragraph" ? readParagraphAttrs(node) : null;
57
- if (paragraphAttrs?.ok) for (const [index, bookmark] of (paragraphAttrs.value.bookmarks ?? []).entries()) registerStart(bookmark.id, `${path}.paragraph.attrs.bookmarks[${index}]`);
71
+ const enclosingParagraph = node.type.name === "paragraph" ? describeParagraph(paragraphAttrs, path) : paragraph;
72
+ const openedAttrBookmarks = [];
73
+ if (paragraphAttrs?.ok) for (const [index, bookmark] of (paragraphAttrs.value.bookmarks ?? []).entries()) {
74
+ const bookmarkPath = `${path}.paragraph.attrs.bookmarks[${index}]`;
75
+ if (registerStart({
76
+ id: bookmark.id,
77
+ name: bookmark.name,
78
+ path: bookmarkPath,
79
+ paragraph: enclosingParagraph,
80
+ origin: "attr"
81
+ })) openedAttrBookmarks.push({
82
+ id: bookmark.id,
83
+ path: bookmarkPath
84
+ });
85
+ }
58
86
  if (node.type.name === "bookmarkBoundary") {
59
87
  const result = readBookmarkBoundaryAttrs(node);
60
88
  if (result.ok) {
61
89
  const attrs = result.value;
62
- const hasHyperlink = node.marks.some((mark) => mark.type.name === "hyperlink");
63
- const trackedChanges = node.marks.filter((mark) => mark.type.name === "insertion" || mark.type.name === "deletion");
64
- if (trackedChanges.length > 1) issues.push({
90
+ if (node.marks.filter((mark) => mark.type.name === "insertion" || mark.type.name === "deletion").length > 1) issues.push({
65
91
  path,
66
- message: "Bookmark boundaries cannot carry multiple tracked-change parents."
92
+ message: `Bookmark boundaries cannot carry multiple tracked-change parents (${enclosingParagraph}).`
67
93
  });
68
- else if (trackedChanges.length === 1 && !hasHyperlink) issues.push({
94
+ if (attrs.type === "start") registerStart({
95
+ id: attrs.id,
96
+ name: attrs.name ?? null,
69
97
  path,
70
- message: "Bookmark boundaries inside tracked changes require a hyperlink serialization parent."
98
+ paragraph: enclosingParagraph,
99
+ origin: "node"
71
100
  });
72
- if (attrs.type === "start") registerStart(attrs.id, path);
73
- else registerEnd(attrs.id, path);
101
+ else registerEnd(attrs.id, path, enclosingParagraph);
74
102
  }
75
103
  }
76
104
  node.forEach((child, _offset, index) => {
77
- visit(child, `${path}.content[${index}]`);
105
+ visit(child, `${path}.content[${index}]`, enclosingParagraph);
78
106
  });
79
- if (paragraphAttrs?.ok) for (const [index, bookmark] of (paragraphAttrs.value.bookmarks ?? []).entries()) registerEnd(bookmark.id, `${path}.paragraph.attrs.bookmarks[${index}]`);
107
+ for (const { id, path: bookmarkPath } of openedAttrBookmarks) registerEnd(id, bookmarkPath, enclosingParagraph);
80
108
  };
81
- visit(doc, "doc");
109
+ visit(doc, "doc", "the document root");
82
110
  for (const boundary of open.values()) issues.push({
83
111
  path: boundary.path,
84
- message: `Bookmark id ${boundary.id} has no matching end boundary.`
112
+ message: `${describeBookmark(boundary.id, boundary.name)} has no matching end boundary (${boundary.paragraph}).`
85
113
  });
86
114
  };
87
115
  const assertValidProseMirrorDocument = (doc, context) => {
@@ -0,0 +1,10 @@
1
+ import { Node } from "prosemirror-model";
2
+ //#region src/prosemirror/zeroWidthAnchors.d.ts
3
+ declare const isZeroWidthAnchor: (node: Node) => boolean;
4
+ /**
5
+ * Whether the textblock holds nothing but zero-width anchors, which is what
6
+ * "everything in it was resolved away" looks like in the document.
7
+ */
8
+ declare const holdsNoContent: (block: Node) => boolean;
9
+ //#endregion
10
+ export { holdsNoContent, isZeroWidthAnchor };
@@ -0,0 +1,23 @@
1
+ import { BOOKMARK_BOUNDARY_NODE_NAME } from "./extensions/nodes/BookmarkBoundaryExtension.js";
2
+ import { RENDERED_PAGE_BREAK_NODE_NAME } from "./extensions/nodes/RenderedPageBreakExtension.js";
3
+ import { TEXT_BOX_ANCHOR_NODE_NAME } from "./extensions/nodes/TextBoxAnchorExtension.js";
4
+ //#region src/prosemirror/zeroWidthAnchors.ts
5
+ const ZERO_WIDTH_ANCHOR_NODE_NAMES = /* @__PURE__ */ new Set([
6
+ BOOKMARK_BOUNDARY_NODE_NAME,
7
+ RENDERED_PAGE_BREAK_NODE_NAME,
8
+ TEXT_BOX_ANCHOR_NODE_NAME
9
+ ]);
10
+ const isZeroWidthAnchor = (node) => ZERO_WIDTH_ANCHOR_NODE_NAMES.has(node.type.name);
11
+ /**
12
+ * Whether the textblock holds nothing but zero-width anchors, which is what
13
+ * "everything in it was resolved away" looks like in the document.
14
+ */
15
+ const holdsNoContent = (block) => {
16
+ let empty = true;
17
+ block.forEach((child) => {
18
+ if (!isZeroWidthAnchor(child)) empty = false;
19
+ });
20
+ return empty;
21
+ };
22
+ //#endregion
23
+ export { holdsNoContent, isZeroWidthAnchor };
package/dist/redline.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { FolioDocxReviewer, isFolioResolvedReviewedView } from "./ai-edits/headless.js";
2
- import { createFolioAITextRangeHandle } from "./ai-edits/snapshot.js";
2
+ import { createFolioAITextRangeHandle, trailingBodyBlockId } from "./ai-edits/snapshot.js";
3
+ import { inlineFormattingSegments } from "./compare/formatting.js";
3
4
  import "./document-operations.js";
4
5
  import { pairFolioDocumentStories } from "./document-stories.js";
5
6
  import { resolveFolioDocumentPrivacyTransforms, rewriteDocxMetadataPrivacy } from "./docx/metadataPrivacy.js";
6
- import { GenerateRedlineDocxOperationLimitError } from "./redlineOperationLimit.js";
7
+ import { GenerateRedlineDocxOperationLimitError, MAX_GENERATED_REDLINE_OPERATIONS } from "./redlineOperationLimit.js";
7
8
  import { alignFolioBlocks } from "./version-comparison.js";
8
9
  import { TaggedError, panic } from "better-result";
9
10
  //#region src/redline.ts
@@ -31,73 +32,15 @@ const nextBaseBlockIdByIndex = (events) => {
31
32
  }
32
33
  return nextIds;
33
34
  };
34
- const changedSupportedFormatting = (base, revised) => ({
35
- ...Boolean(base.bold) !== Boolean(revised.bold) && { bold: Boolean(revised.bold) },
36
- ...Boolean(base.italic) !== Boolean(revised.italic) && { italic: Boolean(revised.italic) },
37
- ...Boolean(base.underline) !== Boolean(revised.underline) && { underline: Boolean(revised.underline) }
38
- });
39
- const sameInlineFormatting = (left, right) => left.bold === right.bold && left.italic === right.italic && left.underline === right.underline;
40
- const hasInlineFormatting = (formatting) => formatting.bold !== void 0 || formatting.italic !== void 0 || formatting.underline !== void 0;
41
- const previewRunsForBlock = (block) => {
42
- const runs = block.previewRuns ?? [{ text: block.text }];
43
- return runs.map(({ text }) => text).join("") === block.text ? runs : null;
44
- };
45
- const formattingSegments = (baseBlock, revisedBlock) => {
46
- const baseRuns = previewRunsForBlock(baseBlock);
47
- const revisedRuns = previewRunsForBlock(revisedBlock);
48
- if (!baseRuns || !revisedRuns || baseBlock.text.length === 0) return [];
49
- const segments = [];
50
- let baseRunIndex = 0;
51
- let revisedRunIndex = 0;
52
- let baseRunOffset = 0;
53
- let revisedRunOffset = 0;
54
- let textOffset = 0;
55
- while (baseRunIndex < baseRuns.length && revisedRunIndex < revisedRuns.length) {
56
- const baseRun = baseRuns[baseRunIndex];
57
- const revisedRun = revisedRuns[revisedRunIndex];
58
- if (!baseRun || !revisedRun) break;
59
- const length = Math.min(baseRun.text.length - baseRunOffset, revisedRun.text.length - revisedRunOffset);
60
- if (length <= 0) {
61
- if (baseRunOffset >= baseRun.text.length) {
62
- baseRunIndex++;
63
- baseRunOffset = 0;
64
- }
65
- if (revisedRunOffset >= revisedRun.text.length) {
66
- revisedRunIndex++;
67
- revisedRunOffset = 0;
68
- }
69
- continue;
70
- }
71
- const formatting = changedSupportedFormatting(baseRun, revisedRun);
72
- if (hasInlineFormatting(formatting)) {
73
- const previous = segments.at(-1);
74
- if (previous && previous.endOffset === textOffset && sameInlineFormatting(previous.formatting, formatting)) previous.endOffset += length;
75
- else {
76
- if (segments.length >= 1e4) throw new GenerateRedlineDocxOperationLimitError({ message: "The document comparison exceeds the generated operation limit." });
77
- segments.push({
78
- startOffset: textOffset,
79
- endOffset: textOffset + length,
80
- formatting
81
- });
82
- }
83
- }
84
- textOffset += length;
85
- baseRunOffset += length;
86
- revisedRunOffset += length;
87
- if (baseRunOffset >= baseRun.text.length) {
88
- baseRunIndex++;
89
- baseRunOffset = 0;
90
- }
91
- if (revisedRunOffset >= revisedRun.text.length) {
92
- revisedRunIndex++;
93
- revisedRunOffset = 0;
94
- }
95
- }
96
- return segments;
97
- };
98
35
  const buildFormattingRedlineOperations = ({ baseBlock, revisedBlock, nextOperationId }) => {
36
+ const segments = inlineFormattingSegments({
37
+ baseBlock,
38
+ targetBlock: revisedBlock,
39
+ maxSegments: MAX_GENERATED_REDLINE_OPERATIONS
40
+ });
41
+ if (segments === null) throw new GenerateRedlineDocxOperationLimitError({ message: "The document comparison exceeds the generated operation limit." });
99
42
  const operations = [];
100
- for (const { startOffset, endOffset, formatting } of formattingSegments(baseBlock, revisedBlock)) {
43
+ for (const { startOffset, endOffset, formatting } of segments) {
101
44
  const range = createFolioAITextRangeHandle({
102
45
  blockId: baseBlock.id,
103
46
  text: baseBlock.text,
@@ -119,7 +62,7 @@ const buildRedlineOperations = ({ baseSnapshot, revisedBlocks, nextOperationId }
119
62
  const anchorIds = nextBaseBlockIdByIndex(events);
120
63
  const operations = [];
121
64
  const trailingAdditions = [];
122
- const lastBaseBlockId = baseSnapshot.blocks.at(-1)?.id ?? null;
65
+ const lastBaseBlockId = trailingBodyBlockId(baseSnapshot);
123
66
  events.forEach((event, eventIndex) => {
124
67
  if (event.type === "pair") {
125
68
  if (event.baseBlock.text !== event.revisedBlock.text) operations.push({
@@ -159,20 +102,10 @@ const buildRedlineOperations = ({ baseSnapshot, revisedBlocks, nextOperationId }
159
102
  ...event.block.styleId !== void 0 && { styleId: event.block.styleId }
160
103
  });
161
104
  });
162
- if (lastBaseBlockId === null && baseSnapshot.emptyDocumentAnchorId !== void 0) {
163
- const firstAddition = trailingAdditions.shift();
164
- if (firstAddition !== void 0) operations.push({
165
- id: nextOperationId(),
166
- type: "replaceBlock",
167
- blockId: baseSnapshot.emptyDocumentAnchorId,
168
- text: firstAddition.text,
169
- ...firstAddition.styleId !== void 0 && { styleId: firstAddition.styleId }
170
- });
171
- }
172
105
  for (const addition of trailingAdditions) operations.push({
173
106
  id: nextOperationId(),
174
107
  type: "insertAfterBlock",
175
- blockId: lastBaseBlockId ?? baseSnapshot.emptyDocumentAnchorId ?? "redline-unanchored",
108
+ blockId: lastBaseBlockId ?? "redline-unanchored",
176
109
  text: addition.text,
177
110
  ...addition.styleId !== void 0 && { styleId: addition.styleId }
178
111
  });
package/dist/server.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult } from "./ai-edits/types.js";
2
- import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationBatchPrecondition, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationQueuedOperation, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
1
+ import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult } from "./ai-edits/types.js";
2
+ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationBatchPrecondition, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationQueuedOperation, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
3
3
  import { FolioReviewChange, FolioReviewChangeKind } from "./ai-edits/read.js";
4
4
  import { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewChangeFilter, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView } from "./ai-edits/headless.js";
5
- import { createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
5
+ import { createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
6
6
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./ai-edits/scoped-reading.js";
7
7
  import { DeriveBlockIdInput, FolioBlockId, deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
8
8
  import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocumentPreset, DocumentStyleSet } from "./style-sets/types.js";
@@ -27,4 +27,4 @@ import { FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_E
27
27
  import { FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioYjsDocxMaterializationError, FolioYjsDocxMaterializationErrorCode, MaterializeYjsDocxOptions, materializeYjsDocx } from "./docx/server/materializeYjsDocx.js";
28
28
  import { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, GenerateRedlineUnprocessedStory, InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx } from "./redline.js";
29
29
  import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FolioBlockDiff, FolioCompareDocxVersionsOptions, FolioDocumentMetadataValue, FolioFormatProperty, FolioMetadataDiff, FolioStoryDiff, FolioVersionBlockHandle, FolioVersionComparisonPrivacyTransform, FolioVersionComparisonScope, FolioVersionDiff, FolioVersionDiffPrivacyOptions, FolioVersionDiffPrivacyReport, FolioVersionDiffSegment, FolioVersionDiffSummaryCounts, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
30
- export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, BILINGUAL_TABLE_LAYOUTS, type BilingualBorders, type BilingualParagraphRef, type BilingualRow, type BilingualRowKind, type BilingualTableLayout, type BilingualTableParagraphRef, type CreateBilingualDocumentOptions, type CreateBilingualDocumentResult, type CreateBilingualDocxOptions, type CreateBilingualDocxResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationBatchPrecondition, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationQueuedOperation, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, FolioYjsDocxMaterializationError, type FolioYjsDocxMaterializationErrorCode, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type MaterializeYjsDocxOptions, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
30
+ export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, BILINGUAL_TABLE_LAYOUTS, type BilingualBorders, type BilingualParagraphRef, type BilingualRow, type BilingualRowKind, type BilingualTableLayout, type BilingualTableParagraphRef, type CreateBilingualDocumentOptions, type CreateBilingualDocumentResult, type CreateBilingualDocxOptions, type CreateBilingualDocxResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationBatchPrecondition, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationQueuedOperation, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, FolioYjsDocxMaterializationError, type FolioYjsDocxMaterializationErrorCode, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type MaterializeYjsDocxOptions, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioDocxReviewer, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView } from "./ai-edits/headless.js";
2
2
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./ai-edits/scoped-reading.js";
3
- import { createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
3
+ import { createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
4
4
  import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
5
5
  import { EnsureParaIdsError, ensureParaIds } from "./docx/ensureParaIds.js";
6
6
  import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./docx/metadataPrivacy.js";
@@ -25,4 +25,4 @@ import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION } from "./style-set
25
25
  import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
26
26
  import { createEmptyDocument } from "./utils/createDocument.js";
27
27
  import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
28
- export { BILINGUAL_TABLE_LAYOUTS, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, FolioYjsDocxMaterializationError, HEADING_LEVELS, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
28
+ export { BILINGUAL_TABLE_LAYOUTS, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, FolioYjsDocxMaterializationError, HEADING_LEVELS, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
@@ -0,0 +1,51 @@
1
+ import { Shaper, ShapingDirection } from "./shaper.js";
2
+ //#region src/shaping/placeRun.d.ts
3
+ /**
4
+ * One glyph, placed. Advances and offsets are in CSS pixels at the run's size;
5
+ * the glyph id belongs to the face that was shaped, not to any subset of it.
6
+ */
7
+ type PlacedGlyph = {
8
+ readonly glyphId: number;
9
+ /**
10
+ * Index into the run's code points of the cluster this glyph belongs to.
11
+ * Several glyphs share it when a cluster shapes to more than one glyph, and
12
+ * one glyph carries it for every code point of a ligature.
13
+ */
14
+ readonly clusterIndex: number;
15
+ readonly xAdvancePx: number;
16
+ readonly xOffsetPx: number;
17
+ readonly yOffsetPx: number;
18
+ };
19
+ type PlaceRunOptions = {
20
+ readonly shaper: Shaper;
21
+ /** sfnt bytes of the binary that covers this text. */
22
+ readonly font: Uint8Array;
23
+ readonly text: string;
24
+ readonly fontSizePx: number;
25
+ /** Omitted means: from the text's own script. */
26
+ readonly direction?: ShapingDirection;
27
+ };
28
+ /** Whether a run's glyphs can be chosen from its code points alone. */
29
+ declare const needsShaping: (text: string) => boolean;
30
+ declare const directionOf: (text: string) => ShapingDirection;
31
+ /**
32
+ * Shape and place one run against one binary.
33
+ *
34
+ * The text must be served by the face's bytes throughout: a run split across
35
+ * subsets of a family is placed one segment at a time, because a glyph id only
36
+ * means anything in the binary it came from.
37
+ */
38
+ declare const placeRun: ({ shaper, font, text, fontSizePx, direction }: PlaceRunOptions) => readonly PlacedGlyph[];
39
+ /**
40
+ * The run's advance spread over its code points, one number each.
41
+ *
42
+ * A cluster's whole advance sits on the first code point that produced it and
43
+ * the rest carry zero. Shaping is what decides how many glyphs a cluster
44
+ * becomes, so there is no per-code-point advance to report: two code points
45
+ * that ligated into one glyph have one width between them, and splitting it
46
+ * would invent a boundary the font does not have. The sum is exact, which is
47
+ * what line breaking and every caller downstream reads.
48
+ */
49
+ declare const advancesPerCodePointPx: (glyphs: readonly PlacedGlyph[], codePointCount: number) => readonly number[];
50
+ //#endregion
51
+ export { PlaceRunOptions, PlacedGlyph, advancesPerCodePointPx, directionOf, needsShaping, placeRun };
@@ -0,0 +1,84 @@
1
+ import { hasComplexScript, hasRightToLeft } from "../utils/scriptSegments.js";
2
+ import { SHAPING_DIRECTION } from "./shaper.js";
3
+ //#region src/shaping/placeRun.ts
4
+ /**
5
+ * Placement: shaped glyphs in the units the rest of folio works in.
6
+ *
7
+ * The shaper answers in font design units and byte offsets. Both consumers want
8
+ * the same answer in CSS pixels and code-point indices, and they must want it
9
+ * from the same code: a measurer that scaled one way and a painter that scaled
10
+ * another would lay out and paint two different pages.
11
+ */
12
+ /** Whether a run's glyphs can be chosen from its code points alone. */
13
+ const needsShaping = (text) => hasComplexScript(text);
14
+ const directionOf = (text) => hasRightToLeft(text) ? SHAPING_DIRECTION.rightToLeft : SHAPING_DIRECTION.leftToRight;
15
+ /**
16
+ * Code-point index for each UTF-8 byte offset the shaper can report as a
17
+ * cluster. Only cluster starts are ever looked up, so offsets inside a
18
+ * character are left absent rather than filled in with a neighbour's index.
19
+ */
20
+ const codePointIndexByByteOffset = (text) => {
21
+ const byOffset = /* @__PURE__ */ new Map();
22
+ let offset = 0;
23
+ let index = 0;
24
+ for (const character of text) {
25
+ byOffset.set(offset, index);
26
+ const codePoint = character.codePointAt(0);
27
+ offset += utf8Length(codePoint);
28
+ index += 1;
29
+ }
30
+ return byOffset;
31
+ };
32
+ const UTF8_TWO_BYTE_MINIMUM = 128;
33
+ const UTF8_THREE_BYTE_MINIMUM = 2048;
34
+ const UTF8_FOUR_BYTE_MINIMUM = 65536;
35
+ const utf8Length = (codePoint) => {
36
+ if (codePoint < UTF8_TWO_BYTE_MINIMUM) return 1;
37
+ if (codePoint < UTF8_THREE_BYTE_MINIMUM) return 2;
38
+ return codePoint < UTF8_FOUR_BYTE_MINIMUM ? 3 : 4;
39
+ };
40
+ /**
41
+ * Shape and place one run against one binary.
42
+ *
43
+ * The text must be served by the face's bytes throughout: a run split across
44
+ * subsets of a family is placed one segment at a time, because a glyph id only
45
+ * means anything in the binary it came from.
46
+ */
47
+ const placeRun = ({ shaper, font, text, fontSizePx, direction }) => {
48
+ const shaped = shaper.shapeRun({
49
+ font,
50
+ text,
51
+ direction: direction ?? directionOf(text)
52
+ });
53
+ if (shaped.unitsPerEm <= 0) return [];
54
+ const scale = fontSizePx / shaped.unitsPerEm;
55
+ const indexByOffset = codePointIndexByByteOffset(text);
56
+ return shaped.glyphs.map(({ glyphId, cluster, xAdvance, xOffset, yOffset }) => ({
57
+ glyphId,
58
+ clusterIndex: indexByOffset.get(cluster) ?? 0,
59
+ xAdvancePx: xAdvance * scale,
60
+ xOffsetPx: xOffset * scale,
61
+ yOffsetPx: yOffset * scale
62
+ }));
63
+ };
64
+ /**
65
+ * The run's advance spread over its code points, one number each.
66
+ *
67
+ * A cluster's whole advance sits on the first code point that produced it and
68
+ * the rest carry zero. Shaping is what decides how many glyphs a cluster
69
+ * becomes, so there is no per-code-point advance to report: two code points
70
+ * that ligated into one glyph have one width between them, and splitting it
71
+ * would invent a boundary the font does not have. The sum is exact, which is
72
+ * what line breaking and every caller downstream reads.
73
+ */
74
+ const advancesPerCodePointPx = (glyphs, codePointCount) => {
75
+ const advances = Array.from({ length: codePointCount }, () => 0);
76
+ for (const { clusterIndex, xAdvancePx } of glyphs) {
77
+ const at = Math.min(clusterIndex, codePointCount - 1);
78
+ if (at < 0) continue;
79
+ advances[at] = (advances[at] ?? 0) + xAdvancePx;
80
+ }
81
+ return advances;
82
+ };
83
+ //#endregion
84
+ export { advancesPerCodePointPx, directionOf, needsShaping, placeRun };
@@ -0,0 +1,92 @@
1
+ //#region src/shaping/shaper.d.ts
2
+ /**
3
+ * The boundary to the text shaper.
4
+ *
5
+ * Shaping is the one part of text layout that cannot be derived from code
6
+ * points: Arabic letters take their form from their neighbours, lam-alef
7
+ * ligates, Devanagari reorders and forms conjuncts, Hebrew points hang off the
8
+ * letter they belong to. One implementation answers that question, in Rust, and
9
+ * every caller reaches it through this module: a measurement in CSS pixels and
10
+ * a PDF text matrix scale the same glyph ids and the same advances, so the two
11
+ * cannot describe different text.
12
+ *
13
+ * The artifact is loaded on demand. It carries the OpenType layout engine and
14
+ * the Unicode data that engine needs, and a document in Latin, Cyrillic or
15
+ * Greek needs none of it: `getShaper` is called only once a run in a shaping
16
+ * script is about to be measured or painted, and never before.
17
+ */
18
+ /** Which way a run advances. Resolved by the producer, never guessed here. */
19
+ declare const SHAPING_DIRECTION: {
20
+ readonly leftToRight: "ltr";
21
+ readonly rightToLeft: "rtl";
22
+ };
23
+ type ShapingDirection = (typeof SHAPING_DIRECTION)[keyof typeof SHAPING_DIRECTION];
24
+ type ShapeRunRequest = {
25
+ /** sfnt bytes of the face to shape with, which must be the face measured. */
26
+ readonly font: Uint8Array;
27
+ /** Index within a font collection; 0 for a plain font file. */
28
+ readonly faceIndex?: number;
29
+ readonly text: string;
30
+ readonly direction: ShapingDirection;
31
+ /** ISO-15924 tag, or omitted to let the shaper detect the script. */
32
+ readonly script?: string;
33
+ /** BCP-47 language, or omitted. Selects locale-specific substitutions. */
34
+ readonly language?: string;
35
+ /** OpenType features to force on, such as `liga` or `kern`. */
36
+ readonly featuresOn?: readonly string[];
37
+ /** Features to force off. */
38
+ readonly featuresOff?: readonly string[];
39
+ };
40
+ /** One shaped glyph, in the face's design units. */
41
+ type ShapedGlyph = {
42
+ /** Glyph id in the face that was shaped, not a code point. */
43
+ readonly glyphId: number;
44
+ /**
45
+ * UTF-8 byte offset into the request's text of the character this glyph came
46
+ * from. A ligature's glyphs share one offset, which is what lets a caller map
47
+ * a glyph back to text for extraction and for a `/ToUnicode` map.
48
+ */
49
+ readonly cluster: number;
50
+ readonly xAdvance: number;
51
+ readonly yAdvance: number;
52
+ readonly xOffset: number;
53
+ readonly yOffset: number;
54
+ };
55
+ type ShapedRun = {
56
+ readonly glyphs: readonly ShapedGlyph[];
57
+ /** Design units per em, so a caller scales without re-reading the face. */
58
+ readonly unitsPerEm: number;
59
+ };
60
+ type Shaper = {
61
+ readonly shapeRun: (request: ShapeRunRequest) => ShapedRun;
62
+ };
63
+ declare const ShaperError_base: import("better-result").TaggedErrorClass<"ShaperError">;
64
+ declare class ShaperError extends ShaperError_base<{
65
+ message: string;
66
+ cause?: unknown;
67
+ }> {}
68
+ /**
69
+ * Loads the shaper, once, and hands back the single implementation both the
70
+ * measurer and the PDF backend use.
71
+ *
72
+ * Calling this is what fetches the artifact, so a caller asks for it only after
73
+ * establishing that some run actually needs shaping.
74
+ */
75
+ declare const getShaper: () => Promise<Shaper>;
76
+ /**
77
+ * Whether the artifact has been loaded in this process.
78
+ *
79
+ * Exists for the test that a document in a non-shaping script never pays for
80
+ * it, which is the whole reason this is a separate artifact.
81
+ */
82
+ declare const shaperLoaded: () => boolean;
83
+ /**
84
+ * The shaper if it is already loaded, and never a load.
85
+ *
86
+ * The measure seam is synchronous: a measurement cannot await an artifact. So
87
+ * whoever is about to measure text that shapes resolves {@link getShaper}
88
+ * first, and the measurer reads the result here.
89
+ */
90
+ declare const resolvedShaper: () => Shaper | null;
91
+ //#endregion
92
+ export { SHAPING_DIRECTION, ShapeRunRequest, ShapedGlyph, ShapedRun, Shaper, ShaperError, ShapingDirection, getShaper, resolvedShaper, shaperLoaded };