@stll/folio-core 0.32.2 → 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 +649 -85
  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 +219 -15
  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 +39 -8
  100. package/dist/document-operations.js +169 -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 +3 -3
  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
@@ -0,0 +1,442 @@
1
+ import { STROKE_DASH_FACTORS, glyphCellOffsetsPx } from "../display-list/primitives.js";
2
+ import { needsShaping } from "../shaping/placeRun.js";
3
+ import { TEXT_RENDER_MODE, createContentStream } from "./contentStream.js";
4
+ import { basePageMatrix, rotationMatrix } from "./pageSpace.js";
5
+ import { Result, TaggedError, panic } from "better-result";
6
+ //#region src/pdf/paint.ts
7
+ /**
8
+ * Display list to content stream.
9
+ *
10
+ * Everything here is emitted in display-list coordinates: CSS px, origin at
11
+ * the page's top-left, y down. The page's base CTM (see `pageSpace.ts`)
12
+ * converts once, at the stream's first operator, which is why no primitive
13
+ * below does any arithmetic on the page height.
14
+ */
15
+ var PaintError = class extends TaggedError("PaintError") {};
16
+ /** Thousandths of an em, the unit of a `TJ` adjustment. */
17
+ const TEXT_SPACE_UNITS_PER_EM = 1e3;
18
+ const OPAQUE = 1;
19
+ /** Points sampled along one period of a `wavy` stroke. */
20
+ const WAVY_SAMPLES_PER_PERIOD = 8;
21
+ /** A `double` stroke is drawn as two lines, one either side of the path. */
22
+ const DOUBLE_STROKE_LINES = 2;
23
+ const withAlpha = (context, alpha) => ({
24
+ ...context,
25
+ alpha
26
+ });
27
+ /**
28
+ * Opens a graphics-state block for one primitive. Alpha is multiplied down
29
+ * from the enclosing groups rather than replaced, so a half-transparent run
30
+ * inside a half-transparent watermark paints at a quarter and not at a half.
31
+ * PDF's `/ca` is per-object, so a group's alpha reaches its children this way
32
+ * instead of compositing the group as a unit; overlapping children inside one
33
+ * opacity group therefore show their seams where a true transparency group
34
+ * would not.
35
+ */
36
+ const beginPrimitive = (context, alpha) => {
37
+ context.stream.save();
38
+ if (alpha < OPAQUE) context.stream.setAlpha(alpha);
39
+ };
40
+ const applyStrokeState = (stream, stroke, widthPx) => {
41
+ stream.setStrokeColor(stroke.color);
42
+ stream.setLineWidth(widthPx);
43
+ const dash = STROKE_DASH_FACTORS[stroke.pattern];
44
+ if (dash !== null) stream.setDash([dash.dash * stroke.thicknessPx, dash.gap * stroke.thicknessPx], 0);
45
+ };
46
+ /** Unit normal of a segment, or null when the segment has no length. */
47
+ const segmentNormal = (segment) => {
48
+ const dx = segment.x2Px - segment.x1Px;
49
+ const dy = segment.y2Px - segment.y1Px;
50
+ const length = Math.hypot(dx, dy);
51
+ if (length === 0) return null;
52
+ return {
53
+ nx: -dy / length,
54
+ ny: dx / length
55
+ };
56
+ };
57
+ const strokeWavy = (stream, stroke, segment) => {
58
+ const normal = segmentNormal(segment);
59
+ if (normal === null) return;
60
+ const dx = segment.x2Px - segment.x1Px;
61
+ const dy = segment.y2Px - segment.y1Px;
62
+ const length = Math.hypot(dx, dy);
63
+ const period = 6 * stroke.thicknessPx;
64
+ const amplitude = 2 * stroke.thicknessPx / 2;
65
+ const steps = Math.max(WAVY_SAMPLES_PER_PERIOD, Math.ceil(length / period * WAVY_SAMPLES_PER_PERIOD));
66
+ for (let step = 0; step <= steps; step += 1) {
67
+ const along = length * step / steps;
68
+ const offset = amplitude * Math.sin(2 * Math.PI * along / period);
69
+ const x = segment.x1Px + dx * step / steps + normal.nx * offset;
70
+ const y = segment.y1Px + dy * step / steps + normal.ny * offset;
71
+ if (step === 0) stream.moveTo(x, y);
72
+ else stream.lineTo(x, y);
73
+ }
74
+ stream.stroke();
75
+ };
76
+ const strokeDouble = (stream, stroke, segment) => {
77
+ const normal = segmentNormal(segment);
78
+ if (normal === null) return;
79
+ const offset = 1 * stroke.thicknessPx * DOUBLE_STROKE_LINES / 2;
80
+ for (const side of [-1, 1]) {
81
+ stream.moveTo(segment.x1Px + normal.nx * offset * side, segment.y1Px + normal.ny * offset * side);
82
+ stream.lineTo(segment.x2Px + normal.nx * offset * side, segment.y2Px + normal.ny * offset * side);
83
+ stream.stroke();
84
+ }
85
+ };
86
+ const paintSegment = (context, stroke, segment) => {
87
+ const alpha = context.alpha * stroke.color.a;
88
+ beginPrimitive(context, alpha);
89
+ const { stream } = context;
90
+ switch (stroke.pattern) {
91
+ case "solid":
92
+ case "dashed":
93
+ case "dotted":
94
+ applyStrokeState(stream, stroke, stroke.thicknessPx);
95
+ stream.moveTo(segment.x1Px, segment.y1Px);
96
+ stream.lineTo(segment.x2Px, segment.y2Px);
97
+ stream.stroke();
98
+ break;
99
+ case "double":
100
+ applyStrokeState(stream, stroke, stroke.thicknessPx);
101
+ strokeDouble(stream, stroke, segment);
102
+ break;
103
+ case "wavy":
104
+ applyStrokeState(stream, stroke, stroke.thicknessPx);
105
+ strokeWavy(stream, stroke, segment);
106
+ break;
107
+ default: {
108
+ const unreachable = stroke.pattern;
109
+ panic(`unhandled stroke pattern: ${String(unreachable)}`);
110
+ }
111
+ }
112
+ stream.restore();
113
+ };
114
+ const paintRect = (context, primitive) => {
115
+ const { rect, fill, stroke } = primitive;
116
+ if (fill !== void 0) {
117
+ beginPrimitive(context, context.alpha * fill.a);
118
+ context.stream.setFillColor(fill);
119
+ context.stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
120
+ context.stream.fill();
121
+ context.stream.restore();
122
+ }
123
+ if (stroke === void 0) return;
124
+ if (stroke.pattern === "solid") {
125
+ beginPrimitive(context, context.alpha * stroke.color.a);
126
+ applyStrokeState(context.stream, stroke, stroke.thicknessPx);
127
+ context.stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
128
+ context.stream.stroke();
129
+ context.stream.restore();
130
+ return;
131
+ }
132
+ const { xPx, yPx, widthPx, heightPx } = rect;
133
+ const edges = [
134
+ {
135
+ x1Px: xPx,
136
+ y1Px: yPx,
137
+ x2Px: xPx + widthPx,
138
+ y2Px: yPx
139
+ },
140
+ {
141
+ x1Px: xPx + widthPx,
142
+ y1Px: yPx,
143
+ x2Px: xPx + widthPx,
144
+ y2Px: yPx + heightPx
145
+ },
146
+ {
147
+ x1Px: xPx + widthPx,
148
+ y1Px: yPx + heightPx,
149
+ x2Px: xPx,
150
+ y2Px: yPx + heightPx
151
+ },
152
+ {
153
+ x1Px: xPx,
154
+ y1Px: yPx + heightPx,
155
+ x2Px: xPx,
156
+ y2Px: yPx
157
+ }
158
+ ];
159
+ for (const edge of edges) paintSegment(context, stroke, edge);
160
+ };
161
+ /**
162
+ * Left edge of every code point, from the ordering the display list pins
163
+ * down. The mirroring rule is shared with the DOM backend rather than stated
164
+ * twice: two backends agreeing by coincidence is the divergence the display
165
+ * list exists to prevent.
166
+ */
167
+ const glyphOrigins = (run) => glyphCellOffsetsPx(run).map((offsetPx) => run.xPx + offsetPx);
168
+ /**
169
+ * Where each glyph of an embedded run goes.
170
+ *
171
+ * Cluster origins come from the display list's own advances whichever path
172
+ * produced the glyphs, so letter spacing and every other adjustment the
173
+ * measurer applied survive: the line was broken on those numbers. What shaping
174
+ * contributes is which glyphs there are and where they sit inside their
175
+ * cluster.
176
+ */
177
+ const placeEmbeddedRun = (run, font, origins) => {
178
+ const shaped = font.placeShapedRun({
179
+ text: run.text,
180
+ direction: run.direction,
181
+ fontSizePx: run.fontSizePx
182
+ });
183
+ if (shaped === null) return [...run.text].map((character, index) => {
184
+ const { resourceIndex, glyphId, widthUnits } = font.glyphFor(character.codePointAt(0) ?? 0);
185
+ return {
186
+ resourceIndex,
187
+ glyphId,
188
+ widthUnits,
189
+ xPx: origins[index] ?? run.xPx,
190
+ yPx: run.baselineYPx,
191
+ advancePx: run.advancesPx[index] ?? 0,
192
+ displaced: false
193
+ };
194
+ });
195
+ const placed = [];
196
+ let penPx = run.xPx;
197
+ let openCluster = null;
198
+ for (const glyph of shaped) {
199
+ if (glyph.clusterIndex !== openCluster) {
200
+ penPx = origins[glyph.clusterIndex] ?? penPx;
201
+ openCluster = glyph.clusterIndex;
202
+ }
203
+ const displaced = glyph.xOffsetPx !== 0 || glyph.yOffsetPx !== 0;
204
+ placed.push({
205
+ resourceIndex: glyph.resourceIndex,
206
+ glyphId: glyph.glyphId,
207
+ widthUnits: glyph.widthUnits,
208
+ xPx: penPx + glyph.xOffsetPx,
209
+ yPx: run.baselineYPx - glyph.yOffsetPx,
210
+ advancePx: glyph.xAdvancePx,
211
+ displaced
212
+ });
213
+ penPx += glyph.xAdvancePx;
214
+ }
215
+ return placed;
216
+ };
217
+ /** Glyphs one text-showing operator can draw together, in painting order. */
218
+ const spanGlyphs = (placed, fontSizePx) => {
219
+ const spans = [];
220
+ for (const [index, glyph] of placed.entries()) {
221
+ const next = placed[index + 1];
222
+ const open = spans.at(-1);
223
+ const continues = open !== void 0 && !glyph.displaced && open.resourceIndex === glyph.resourceIndex && open.yPx === glyph.yPx;
224
+ const delta = next === void 0 || next.displaced || next.yPx !== glyph.yPx ? glyph.advancePx : next.xPx - glyph.xPx;
225
+ const positioned = {
226
+ glyphId: glyph.glyphId,
227
+ adjustment: glyph.widthUnits - delta * TEXT_SPACE_UNITS_PER_EM / fontSizePx
228
+ };
229
+ if (continues && open !== void 0) {
230
+ open.glyphs.push(positioned);
231
+ continue;
232
+ }
233
+ spans.push({
234
+ resourceIndex: glyph.resourceIndex,
235
+ xPx: glyph.xPx,
236
+ yPx: glyph.yPx,
237
+ glyphs: [positioned]
238
+ });
239
+ }
240
+ return spans;
241
+ };
242
+ const paintGlyphRun = (context, run) => {
243
+ const codePoints = [...run.text];
244
+ if (codePoints.length === 0 || run.fontSizePx <= 0) return;
245
+ const font = context.fonts.get(run.font) ?? panic(`glyph run refers to unprepared font ${String(run.font)}`);
246
+ const origins = glyphOrigins(run);
247
+ const { stream } = context;
248
+ beginPrimitive(context, context.alpha * run.color.a);
249
+ stream.setFillColor(run.color);
250
+ if (run.stroke !== void 0) {
251
+ stream.setStrokeColor(run.stroke.color);
252
+ stream.setLineWidth(run.stroke.thicknessPx);
253
+ }
254
+ stream.beginText();
255
+ switch (font.kind) {
256
+ case "embedded": {
257
+ const spans = spanGlyphs(placeEmbeddedRun(run, font, origins), run.fontSizePx);
258
+ for (const [spanIndex, span] of spans.entries()) {
259
+ stream.setFont(span.resourceIndex, run.fontSizePx);
260
+ if (spanIndex === 0 && run.stroke !== void 0) stream.setTextRenderMode(TEXT_RENDER_MODE.fillThenStroke);
261
+ stream.setTextMatrix([
262
+ 1,
263
+ 0,
264
+ 0,
265
+ -1,
266
+ span.xPx,
267
+ span.yPx
268
+ ]);
269
+ stream.showGlyphs(span.glyphs);
270
+ }
271
+ break;
272
+ }
273
+ case "standard":
274
+ stream.setFont(font.resourceIndex, run.fontSizePx);
275
+ if (run.stroke !== void 0) stream.setTextRenderMode(TEXT_RENDER_MODE.fillThenStroke);
276
+ for (const [index, codePoint] of codePoints.entries()) {
277
+ stream.setTextMatrix([
278
+ 1,
279
+ 0,
280
+ 0,
281
+ -1,
282
+ origins[index] ?? run.xPx,
283
+ run.baselineYPx
284
+ ]);
285
+ stream.showBytes([font.byteFor(codePoint.codePointAt(0) ?? 0)]);
286
+ }
287
+ break;
288
+ default: panic(`unhandled prepared font: ${JSON.stringify(font)}`);
289
+ }
290
+ stream.endText();
291
+ stream.restore();
292
+ };
293
+ const paintImage = (context, primitive) => {
294
+ const { rect, crop, opacity } = primitive;
295
+ const { stream } = context;
296
+ beginPrimitive(context, context.alpha * opacity);
297
+ if (crop === void 0) {
298
+ stream.concat([
299
+ rect.widthPx,
300
+ 0,
301
+ 0,
302
+ -rect.heightPx,
303
+ rect.xPx,
304
+ rect.yPx + rect.heightPx
305
+ ]);
306
+ stream.drawImage(primitive.image);
307
+ stream.restore();
308
+ return;
309
+ }
310
+ const visibleWidth = 1 - crop.l - crop.r;
311
+ const visibleHeight = 1 - crop.t - crop.b;
312
+ if (visibleWidth <= 0 || visibleHeight <= 0) {
313
+ stream.restore();
314
+ return;
315
+ }
316
+ stream.appendRect(rect.xPx, rect.yPx, rect.widthPx, rect.heightPx);
317
+ stream.clipToCurrentPath();
318
+ const fullWidth = rect.widthPx / visibleWidth;
319
+ const fullHeight = rect.heightPx / visibleHeight;
320
+ const left = rect.xPx - crop.l * fullWidth;
321
+ const top = rect.yPx - crop.t * fullHeight;
322
+ stream.concat([
323
+ fullWidth,
324
+ 0,
325
+ 0,
326
+ -fullHeight,
327
+ left,
328
+ top + fullHeight
329
+ ]);
330
+ stream.drawImage(primitive.image);
331
+ stream.restore();
332
+ };
333
+ const paintPrimitive = (context, primitive) => {
334
+ switch (primitive.kind) {
335
+ case "glyphRun":
336
+ paintGlyphRun(context, primitive);
337
+ return;
338
+ case "rect":
339
+ paintRect(context, primitive);
340
+ return;
341
+ case "line":
342
+ paintSegment(context, primitive.stroke, {
343
+ x1Px: primitive.x1Px,
344
+ y1Px: primitive.y1Px,
345
+ x2Px: primitive.x2Px,
346
+ y2Px: primitive.y2Px
347
+ });
348
+ return;
349
+ case "image":
350
+ paintImage(context, primitive);
351
+ return;
352
+ case "clipGroup":
353
+ context.stream.save();
354
+ context.stream.appendRect(primitive.rect.xPx, primitive.rect.yPx, primitive.rect.widthPx, primitive.rect.heightPx);
355
+ context.stream.clipToCurrentPath();
356
+ for (const child of primitive.children) paintPrimitive(context, child);
357
+ context.stream.restore();
358
+ return;
359
+ case "rotateGroup":
360
+ context.stream.save();
361
+ context.stream.concat(rotationMatrix(primitive.degrees, primitive.originXPx, primitive.originYPx));
362
+ for (const child of primitive.children) paintPrimitive(context, child);
363
+ context.stream.restore();
364
+ return;
365
+ case "opacityGroup": {
366
+ const alpha = context.alpha * primitive.opacity;
367
+ context.stream.save();
368
+ context.stream.setAlpha(alpha);
369
+ const inner = withAlpha(context, alpha);
370
+ for (const child of primitive.children) paintPrimitive(inner, child);
371
+ context.stream.restore();
372
+ return;
373
+ }
374
+ default: panic(`unhandled display primitive: ${JSON.stringify(primitive)}`);
375
+ }
376
+ };
377
+ const paintPage = ({ page, fonts }) => {
378
+ const stream = createContentStream();
379
+ stream.concat(basePageMatrix(page.heightPx));
380
+ const context = {
381
+ stream,
382
+ fonts,
383
+ alpha: OPAQUE
384
+ };
385
+ for (const primitive of page.primitives) paintPrimitive(context, primitive);
386
+ return stream.parts();
387
+ };
388
+ const collectPrimitive = (primitive, fontCount, imageCount, into) => {
389
+ switch (primitive.kind) {
390
+ case "glyphRun": {
391
+ if (primitive.font < 0 || primitive.font >= fontCount) return new PaintError({ message: `glyph run refers to font ${String(primitive.font)}, outside the font table` });
392
+ const codePoints = [...primitive.text];
393
+ if (codePoints.length !== primitive.advancesPx.length) return new PaintError({ message: `glyph run carries ${String(primitive.advancesPx.length)} advances for ${String(codePoints.length)} code points` });
394
+ const seen = into.codePointsByFont.get(primitive.font) ?? /* @__PURE__ */ new Set();
395
+ for (const codePoint of codePoints) seen.add(codePoint.codePointAt(0) ?? 0);
396
+ into.codePointsByFont.set(primitive.font, seen);
397
+ if (needsShaping(primitive.text)) {
398
+ const runs = into.shapedRunsByFont.get(primitive.font) ?? [];
399
+ runs.push({
400
+ text: primitive.text,
401
+ direction: primitive.direction,
402
+ fontSizePx: primitive.fontSizePx
403
+ });
404
+ into.shapedRunsByFont.set(primitive.font, runs);
405
+ }
406
+ return null;
407
+ }
408
+ case "rect":
409
+ case "line": return null;
410
+ case "image":
411
+ if (primitive.image < 0 || primitive.image >= imageCount) return new PaintError({ message: `image primitive refers to image ${String(primitive.image)}, outside the image table` });
412
+ into.imageIndices.add(primitive.image);
413
+ return null;
414
+ case "clipGroup":
415
+ case "rotateGroup":
416
+ case "opacityGroup":
417
+ for (const child of primitive.children) {
418
+ const error = collectPrimitive(child, fontCount, imageCount, into);
419
+ if (error !== null) return error;
420
+ }
421
+ return null;
422
+ default: return panic(`unhandled display primitive: ${JSON.stringify(primitive)}`);
423
+ }
424
+ };
425
+ /**
426
+ * Walks the pages once before anything is written, because subsetting needs
427
+ * every glyph the document uses before the first one is emitted.
428
+ */
429
+ const collectUsage = (pages, fontCount, imageCount) => {
430
+ const into = {
431
+ codePointsByFont: /* @__PURE__ */ new Map(),
432
+ shapedRunsByFont: /* @__PURE__ */ new Map(),
433
+ imageIndices: /* @__PURE__ */ new Set()
434
+ };
435
+ for (const page of pages) for (const primitive of page.primitives) {
436
+ const error = collectPrimitive(primitive, fontCount, imageCount, into);
437
+ if (error !== null) return Result.err(error);
438
+ }
439
+ return Result.ok(into);
440
+ };
441
+ //#endregion
442
+ export { PaintError, collectUsage, paintPage };
@@ -0,0 +1,56 @@
1
+ import { DisplayList } from "../display-list/types.js";
2
+ import { PdfFontSource, PdfSubstitution } from "./fonts.js";
3
+ import { Result } from "better-result";
4
+ //#region src/pdf/writePdf.d.ts
5
+ type WritePdfOptions = {
6
+ readonly fonts: PdfFontSource;
7
+ /**
8
+ * Creation and modification instant stamped into the PDF, ISO-8601.
9
+ * Required rather than defaulted so a caller cannot get a
10
+ * nondeterministic document by omission.
11
+ */
12
+ readonly timestamp: string;
13
+ readonly producer?: string;
14
+ /**
15
+ * Refuse to write a document containing a code point no supplied face can
16
+ * encode, instead of painting `.notdef` and reporting it. For a caller that
17
+ * would rather fail than hand a reader a page of empty boxes.
18
+ */
19
+ readonly strictGlyphCoverage?: boolean;
20
+ };
21
+ /**
22
+ * A code point no binary of its face could encode. It is painted as `.notdef`
23
+ * and reported here, never dropped and never silently substituted: a reader
24
+ * cannot tell an empty box from a character the document never had, and every
25
+ * such code point collapses onto glyph 0, so even `/ToUnicode` cannot tell
26
+ * them apart afterwards.
27
+ */
28
+ type PdfUnencodable = {
29
+ readonly codePoint: number;
30
+ readonly family: string;
31
+ readonly weight: number;
32
+ readonly italic: boolean;
33
+ };
34
+ type WritePdfResult = {
35
+ readonly bytes: Uint8Array;
36
+ /** Faces the font source could not supply, painted with a base-14 stand-in. */
37
+ readonly substitutions: readonly PdfSubstitution[];
38
+ /** Code points painted as `.notdef` because no supplied face covers them. */
39
+ readonly unencodable: readonly PdfUnencodable[];
40
+ };
41
+ declare const WritePdfError_base: import("better-result").TaggedErrorClass<"WritePdfError">;
42
+ declare class WritePdfError extends WritePdfError_base<{
43
+ message: string;
44
+ cause?: unknown;
45
+ }> {}
46
+ /**
47
+ * Writes the display list as a PDF.
48
+ *
49
+ * Asynchronous for one reason: a run in a script that shapes needs the shaper,
50
+ * and the shaper is fetched the first time a document actually contains one. A
51
+ * document in Latin, Cyrillic or Greek never awaits anything but the resolved
52
+ * promise this returns.
53
+ */
54
+ declare const writePdf: (list: DisplayList, options: WritePdfOptions) => Promise<Result<WritePdfResult, WritePdfError>>;
55
+ //#endregion
56
+ export { type PdfFontSource, type PdfSubstitution, PdfUnencodable, WritePdfError, WritePdfOptions, WritePdfResult, writePdf };