@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
@@ -0,0 +1,525 @@
1
+ import { SFNT_TABLE, SFNT_VERSION, fitsInView, readLocaOffsets, readSfntDirectory, readSfntVersion } from "./tables.js";
2
+ import { Result, TaggedError } from "better-result";
3
+ //#region src/fonts/sfnt/parse.ts
4
+ /**
5
+ * A self-contained TrueType/OpenType (sfnt) reader.
6
+ *
7
+ * It reads exactly what text measurement and PDF embedding need: the metrics
8
+ * head/hhea/OS-2 carry, a character-to-glyph lookup, and advance widths. It
9
+ * does not decode outlines (the subsetter copies `glyf` bytes verbatim) and it
10
+ * does not hint, shape or rasterize.
11
+ *
12
+ * Robustness contract: every offset and length is checked against the buffer
13
+ * before it is read, so a truncated or lying font yields an `Err` rather than
14
+ * a throw, and no field is ever `NaN`.
15
+ */
16
+ var SfntParseError = class extends TaggedError("SfntParseError") {};
17
+ const fail = (message) => Result.err(new SfntParseError({ message }));
18
+ /** Byte offsets inside `head`. */
19
+ const HEAD = {
20
+ checkSumAdjustment: 8,
21
+ magicNumber: 12,
22
+ unitsPerEm: 18,
23
+ xMin: 36,
24
+ yMin: 38,
25
+ xMax: 40,
26
+ yMax: 42,
27
+ indexToLocFormat: 50,
28
+ size: 54
29
+ };
30
+ /** `head.magicNumber`, the one field that proves the table is really `head`. */
31
+ const HEAD_MAGIC = 1594834165;
32
+ const MIN_UNITS_PER_EM = 16;
33
+ const MAX_UNITS_PER_EM = 16384;
34
+ /** Byte offsets inside `hhea`. */
35
+ const HHEA = {
36
+ ascender: 4,
37
+ descender: 6,
38
+ lineGap: 8,
39
+ numberOfHMetrics: 34,
40
+ size: 36
41
+ };
42
+ /** Byte offsets inside `maxp` (only the half both versions share). */
43
+ const MAXP = {
44
+ numGlyphs: 4,
45
+ size: 6
46
+ };
47
+ /** Byte offsets inside `OS/2`. */
48
+ const OS2 = {
49
+ version: 0,
50
+ fsType: 8,
51
+ sxHeight: 86,
52
+ sCapHeight: 88,
53
+ /** Version 2 is the first that has sxHeight/sCapHeight at all. */
54
+ minimumVersionWithHeights: 2,
55
+ sizeWithHeights: 96
56
+ };
57
+ /** Byte offsets inside `post`. */
58
+ const POST = {
59
+ italicAngle: 4,
60
+ size: 32
61
+ };
62
+ /** 16.16 fixed-point, as `post.italicAngle` and table versions are stored. */
63
+ const FIXED_POINT_DIVISOR = 65536;
64
+ /** One hmtx `longHorMetric`: advanceWidth (uint16) then lsb (int16). */
65
+ const HMTX_METRIC_SIZE = 4;
66
+ /** Byte offsets inside a glyph description header. */
67
+ const GLYPH_HEADER = {
68
+ numberOfContours: 0,
69
+ xMin: 2,
70
+ yMin: 4,
71
+ xMax: 6,
72
+ yMax: 8,
73
+ size: 10
74
+ };
75
+ /**
76
+ * Cap and x-height fallbacks for fonts without OS/2 version 2 whose 'H' and
77
+ * 'x' cannot be measured (no cmap, no outline, or CFF outlines this reader
78
+ * does not decode). Ratios of the em, rounded, so measurement stays sane.
79
+ */
80
+ const CAP_HEIGHT_EM_RATIO = .7;
81
+ const X_HEIGHT_EM_RATIO = .5;
82
+ const CAP_HEIGHT_SAMPLE = 72;
83
+ const X_HEIGHT_SAMPLE = 120;
84
+ const NAME = {
85
+ count: 2,
86
+ stringOffset: 4,
87
+ recordsStart: 6,
88
+ recordSize: 12,
89
+ size: 6
90
+ };
91
+ const NAME_RECORD = {
92
+ platformId: 0,
93
+ encodingId: 2,
94
+ languageId: 4,
95
+ nameId: 6,
96
+ length: 8,
97
+ offset: 10
98
+ };
99
+ /** name id 6 is the PostScript name. */
100
+ const POSTSCRIPT_NAME_ID = 6;
101
+ const PLATFORM = {
102
+ unicode: 0,
103
+ macintosh: 1,
104
+ windows: 3
105
+ };
106
+ const WINDOWS_ENCODING = {
107
+ symbol: 0,
108
+ bmp: 1,
109
+ full: 10
110
+ };
111
+ const CMAP = {
112
+ numTables: 2,
113
+ recordsStart: 4,
114
+ recordSize: 8
115
+ };
116
+ const CMAP_RECORD = {
117
+ platformId: 0,
118
+ encodingId: 2,
119
+ offset: 4
120
+ };
121
+ const CMAP_FORMAT = {
122
+ /** Segment mapping to delta values: the BMP workhorse. */
123
+ segmentMapping: 4,
124
+ /** Trimmed table mapping: a single contiguous run. */
125
+ trimmedTable: 6,
126
+ /** Segmented coverage: the full Unicode range, uint32 code points. */
127
+ segmentedCoverage: 12
128
+ };
129
+ /** Byte offsets inside a format 4 subtable. */
130
+ const FORMAT_4 = {
131
+ segCountX2: 6,
132
+ endCodes: 14,
133
+ headerSize: 14
134
+ };
135
+ /** Byte offsets inside a format 6 subtable. */
136
+ const FORMAT_6 = {
137
+ firstCode: 6,
138
+ entryCount: 8,
139
+ glyphIds: 10,
140
+ headerSize: 10
141
+ };
142
+ /** Byte offsets inside a format 12 subtable. */
143
+ const FORMAT_12 = {
144
+ numGroups: 12,
145
+ groups: 16,
146
+ headerSize: 16,
147
+ groupSize: 12
148
+ };
149
+ /**
150
+ * Symbol fonts (platform 3, encoding 0) map their glyphs into the private use
151
+ * area, so a lookup for 'A' has to be retried as `0xF000 | 'A'`.
152
+ */
153
+ const SYMBOL_CODEPOINT_BASE = 61440;
154
+ /**
155
+ * Ceiling on entries materialized from a format 6/12 subtable. Glyph ids are
156
+ * uint16, so an honest font cannot map more code points than this; a font that
157
+ * claims to is lying about its group ranges and must not be allowed to
158
+ * allocate against it.
159
+ */
160
+ const MAX_CMAP_MAPPINGS = 1 << 18;
161
+ const MAX_UNICODE_CODE_POINT = 1114111;
162
+ /** Reads `count` big-endian uint16s into a typed array. Bounds pre-checked. */
163
+ const readUint16Array = (view, offset, count) => {
164
+ const values = new Uint16Array(count);
165
+ for (let index = 0; index < count; index++) values[index] = view.getUint16(offset + index * 2);
166
+ return values;
167
+ };
168
+ const readInt16Array = (view, offset, count) => {
169
+ const values = new Int16Array(count);
170
+ for (let index = 0; index < count; index++) values[index] = view.getInt16(offset + index * 2);
171
+ return values;
172
+ };
173
+ /**
174
+ * Format 4: four parallel segment arrays plus a trailing glyph id array that
175
+ * segments address by a byte offset from their own `idRangeOffset` slot. The
176
+ * arrays are read once here; lookup is a binary search over `endCodes`.
177
+ */
178
+ const buildFormat4Lookup = ({ view, start, end }) => {
179
+ if (!fitsInView(view, start, FORMAT_4.headerSize) || start + FORMAT_4.headerSize > end) return;
180
+ const segCountX2 = view.getUint16(start + FORMAT_4.segCountX2);
181
+ const segCount = segCountX2 >> 1;
182
+ if (segCount === 0) return;
183
+ const endCodesBase = start + FORMAT_4.endCodes;
184
+ const startCodesBase = endCodesBase + segCountX2 + 2;
185
+ const idDeltasBase = startCodesBase + segCountX2;
186
+ const idRangeOffsetsBase = idDeltasBase + segCountX2;
187
+ const arraysSize = segCountX2 * 4 + 2;
188
+ if (!fitsInView(view, endCodesBase, arraysSize) || endCodesBase + arraysSize > end) return;
189
+ const endCodes = readUint16Array(view, endCodesBase, segCount);
190
+ const startCodes = readUint16Array(view, startCodesBase, segCount);
191
+ const idDeltas = readInt16Array(view, idDeltasBase, segCount);
192
+ const idRangeOffsets = readUint16Array(view, idRangeOffsetsBase, segCount);
193
+ return (codePoint) => {
194
+ if (codePoint < 0 || codePoint > 65535) return 0;
195
+ let low = 0;
196
+ let high = segCount - 1;
197
+ let segment = -1;
198
+ while (low <= high) {
199
+ const middle = low + high >> 1;
200
+ if ((endCodes[middle] ?? 0) < codePoint) low = middle + 1;
201
+ else {
202
+ segment = middle;
203
+ high = middle - 1;
204
+ }
205
+ }
206
+ if (segment < 0) return 0;
207
+ const segmentStart = startCodes[segment] ?? 0;
208
+ if (segmentStart > codePoint) return 0;
209
+ const delta = idDeltas[segment] ?? 0;
210
+ const rangeOffset = idRangeOffsets[segment] ?? 0;
211
+ if (rangeOffset === 0) return codePoint + delta & 65535;
212
+ const address = idRangeOffsetsBase + segment * 2 + rangeOffset + (codePoint - segmentStart) * 2;
213
+ if (!fitsInView(view, address, 2) || address + 2 > end) return 0;
214
+ const glyphId = view.getUint16(address);
215
+ return glyphId === 0 ? 0 : glyphId + delta & 65535;
216
+ };
217
+ };
218
+ /** Format 6: one contiguous run of glyph ids, materialized eagerly. */
219
+ const buildFormat6Lookup = ({ view, start, end }) => {
220
+ if (!fitsInView(view, start, FORMAT_6.headerSize) || start + FORMAT_6.headerSize > end) return;
221
+ const firstCode = view.getUint16(start + FORMAT_6.firstCode);
222
+ const entryCount = view.getUint16(start + FORMAT_6.entryCount);
223
+ const glyphIdsBase = start + FORMAT_6.glyphIds;
224
+ if (!fitsInView(view, glyphIdsBase, entryCount * 2) || glyphIdsBase + entryCount * 2 > end) return;
225
+ const mappings = /* @__PURE__ */ new Map();
226
+ for (let index = 0; index < entryCount; index++) {
227
+ const glyphId = view.getUint16(glyphIdsBase + index * 2);
228
+ if (glyphId !== 0) mappings.set(firstCode + index, glyphId);
229
+ }
230
+ return (codePoint) => mappings.get(codePoint) ?? 0;
231
+ };
232
+ /**
233
+ * Format 12: sorted `(startCharCode, endCharCode, startGlyphID)` groups. Every
234
+ * group is expanded into the map up to {@link MAX_CMAP_MAPPINGS}, so lookup is
235
+ * a hash hit rather than a per-character binary search.
236
+ */
237
+ const buildFormat12Lookup = ({ view, start, end }) => {
238
+ if (!fitsInView(view, start, FORMAT_12.headerSize) || start + FORMAT_12.headerSize > end) return;
239
+ const numGroups = view.getUint32(start + FORMAT_12.numGroups);
240
+ const groupsBase = start + FORMAT_12.groups;
241
+ const groupsSize = numGroups * FORMAT_12.groupSize;
242
+ if (!fitsInView(view, groupsBase, groupsSize) || groupsBase + groupsSize > end) return;
243
+ const mappings = /* @__PURE__ */ new Map();
244
+ for (let index = 0; index < numGroups; index++) {
245
+ const group = groupsBase + index * FORMAT_12.groupSize;
246
+ const startCharCode = view.getUint32(group);
247
+ const endCharCode = view.getUint32(group + 4);
248
+ const startGlyphId = view.getUint32(group + 8);
249
+ if (endCharCode < startCharCode || endCharCode > MAX_UNICODE_CODE_POINT) return;
250
+ if (mappings.size + (endCharCode - startCharCode + 1) > MAX_CMAP_MAPPINGS) return;
251
+ for (let codePoint = startCharCode; codePoint <= endCharCode; codePoint++) {
252
+ const glyphId = startGlyphId + (codePoint - startCharCode);
253
+ if (glyphId !== 0) mappings.set(codePoint, glyphId);
254
+ }
255
+ }
256
+ return (codePoint) => mappings.get(codePoint) ?? 0;
257
+ };
258
+ const buildLookup = (subtable) => {
259
+ switch (subtable.format) {
260
+ case CMAP_FORMAT.segmentMapping: return buildFormat4Lookup(subtable);
261
+ case CMAP_FORMAT.trimmedTable: return buildFormat6Lookup(subtable);
262
+ case CMAP_FORMAT.segmentedCoverage: return buildFormat12Lookup(subtable);
263
+ default: return;
264
+ }
265
+ };
266
+ /**
267
+ * Subtable preference, most specific first. Order is the whole contract here:
268
+ * a full-coverage Windows table beats the BMP one, a Unicode-platform table
269
+ * beats the symbol encoding, and the symbol encoding is the only one whose
270
+ * lookup retries in the private use area.
271
+ */
272
+ const CMAP_PREFERENCES = [
273
+ {
274
+ matches: (subtable) => subtable.platformId === PLATFORM.windows && subtable.encodingId === WINDOWS_ENCODING.full && subtable.format === CMAP_FORMAT.segmentedCoverage,
275
+ symbol: false
276
+ },
277
+ {
278
+ matches: (subtable) => subtable.platformId === PLATFORM.windows && subtable.encodingId === WINDOWS_ENCODING.bmp && subtable.format === CMAP_FORMAT.segmentMapping,
279
+ symbol: false
280
+ },
281
+ {
282
+ matches: (subtable) => subtable.platformId === PLATFORM.unicode && (subtable.format === CMAP_FORMAT.segmentedCoverage || subtable.format === CMAP_FORMAT.segmentMapping),
283
+ symbol: false
284
+ },
285
+ {
286
+ matches: (subtable) => subtable.platformId === PLATFORM.windows && subtable.encodingId === WINDOWS_ENCODING.symbol && subtable.format === CMAP_FORMAT.segmentMapping,
287
+ symbol: true
288
+ },
289
+ {
290
+ matches: (subtable) => subtable.platformId === PLATFORM.macintosh || subtable.platformId === PLATFORM.unicode,
291
+ symbol: false
292
+ }
293
+ ];
294
+ const listCmapSubtables = (view, cmap) => {
295
+ const end = cmap.offset + cmap.length;
296
+ if (!fitsInView(view, cmap.offset, CMAP.recordsStart)) return [];
297
+ const numTables = view.getUint16(cmap.offset + CMAP.numTables);
298
+ const subtables = [];
299
+ for (let index = 0; index < numTables; index++) {
300
+ const record = cmap.offset + CMAP.recordsStart + index * CMAP.recordSize;
301
+ if (record + CMAP.recordSize > end || !fitsInView(view, record, CMAP.recordSize)) break;
302
+ const start = cmap.offset + view.getUint32(record + CMAP_RECORD.offset);
303
+ if (!fitsInView(view, start, 2) || start + 2 > end) continue;
304
+ subtables.push({
305
+ platformId: view.getUint16(record + CMAP_RECORD.platformId),
306
+ encodingId: view.getUint16(record + CMAP_RECORD.encodingId),
307
+ format: view.getUint16(start),
308
+ start,
309
+ end,
310
+ view
311
+ });
312
+ }
313
+ return subtables;
314
+ };
315
+ /**
316
+ * Picks the best subtable the preference order admits and builds its lookup
317
+ * once. A `cmap` that exists but yields no usable subtable is an error: the
318
+ * alternative is a font that silently maps every character to .notdef.
319
+ */
320
+ const buildCmapLookup = (view, cmap) => {
321
+ const subtables = listCmapSubtables(view, cmap);
322
+ for (const preference of CMAP_PREFERENCES) for (const subtable of subtables) {
323
+ if (!preference.matches(subtable)) continue;
324
+ const lookup = buildLookup(subtable);
325
+ if (!lookup) continue;
326
+ if (!preference.symbol) return Result.ok(lookup);
327
+ return Result.ok((codePoint) => {
328
+ const glyphId = lookup(codePoint);
329
+ return glyphId === 0 ? lookup(SYMBOL_CODEPOINT_BASE | codePoint) : glyphId;
330
+ });
331
+ }
332
+ const formats = subtables.map((subtable) => subtable.format).join(", ");
333
+ return fail(`cmap has no subtable this reader can use (found ${subtables.length} subtable(s), format(s): ${formats || "none"})`);
334
+ };
335
+ /** Strips the delimiters a PDF name cannot carry and caps the spec's length. */
336
+ const MAX_POSTSCRIPT_NAME_LENGTH = 63;
337
+ const POSTSCRIPT_NAME_DELIMITERS = /* @__PURE__ */ new Set("()<>[]{}/% ");
338
+ const sanitizePostScriptName = (raw) => {
339
+ let name = "";
340
+ for (const character of raw) {
341
+ const code = character.codePointAt(0) ?? 0;
342
+ if (code > 32 && code < 127 && !POSTSCRIPT_NAME_DELIMITERS.has(character)) name += character;
343
+ }
344
+ return name.slice(0, MAX_POSTSCRIPT_NAME_LENGTH);
345
+ };
346
+ const decodeUtf16Be = (view, offset, length) => {
347
+ let text = "";
348
+ for (let index = 0; index + 1 < length; index += 2) text += String.fromCharCode(view.getUint16(offset + index));
349
+ return text;
350
+ };
351
+ const decodeMacRomanAscii = (view, offset, length) => {
352
+ let text = "";
353
+ for (let index = 0; index < length; index++) {
354
+ const code = view.getUint8(offset + index);
355
+ text += code < 128 ? String.fromCharCode(code) : "";
356
+ }
357
+ return text;
358
+ };
359
+ /**
360
+ * name id 6, preferring the Windows UTF-16BE record over the Macintosh one.
361
+ * Returns `""` when the table is absent, unreadable, or carries no usable
362
+ * PostScript name; the caller substitutes a synthetic name.
363
+ */
364
+ const readPostScriptName = (view, name) => {
365
+ const end = name.offset + name.length;
366
+ if (!fitsInView(view, name.offset, NAME.size)) return "";
367
+ const count = view.getUint16(name.offset + NAME.count);
368
+ const stringsBase = name.offset + view.getUint16(name.offset + NAME.stringOffset);
369
+ let windows = "";
370
+ let macintosh = "";
371
+ for (let index = 0; index < count; index++) {
372
+ const record = name.offset + NAME.recordsStart + index * NAME.recordSize;
373
+ if (record + NAME.recordSize > end || !fitsInView(view, record, NAME.recordSize)) break;
374
+ if (view.getUint16(record + NAME_RECORD.nameId) !== POSTSCRIPT_NAME_ID) continue;
375
+ const platformId = view.getUint16(record + NAME_RECORD.platformId);
376
+ const length = view.getUint16(record + NAME_RECORD.length);
377
+ const offset = stringsBase + view.getUint16(record + NAME_RECORD.offset);
378
+ if (!fitsInView(view, offset, length) || offset + length > end) continue;
379
+ if (platformId === PLATFORM.windows && windows === "") windows = decodeUtf16Be(view, offset, length);
380
+ else if (platformId === PLATFORM.macintosh && macintosh === "") macintosh = decodeMacRomanAscii(view, offset, length);
381
+ }
382
+ return sanitizePostScriptName(windows) || sanitizePostScriptName(macintosh);
383
+ };
384
+ /**
385
+ * A stable stand-in when the font carries no PostScript name. Derived from
386
+ * `head.checkSumAdjustment`, which is a function of the whole file, so the
387
+ * same bytes always produce the same name and two different faces do not
388
+ * collide in a PDF resource dictionary.
389
+ */
390
+ const syntheticPostScriptName = (checkSumAdjustment) => `SfntFont-${checkSumAdjustment.toString(16).toUpperCase().padStart(8, "0")}`;
391
+ /**
392
+ * Builds the `glyphBoundsFor` accessor.
393
+ *
394
+ * A composite glyph carries its own bounds in the same header fields as a
395
+ * simple one, so no component recursion is needed. A CFF font returns null
396
+ * for every glyph: charstring interpretation is out of scope, and the caller
397
+ * has to fall back on the hhea metrics.
398
+ */
399
+ const buildGlyphBoundsReader = ({ directory, numGlyphs, longLoca }) => {
400
+ const loca = directory.tables.get(SFNT_TABLE.loca);
401
+ const glyf = directory.tables.get(SFNT_TABLE.glyf);
402
+ const offsets = loca && glyf ? readLocaOffsets({
403
+ view: directory.view,
404
+ loca,
405
+ numGlyphs,
406
+ longFormat: longLoca
407
+ }) : void 0;
408
+ if (!glyf || !offsets) return () => null;
409
+ const { view } = directory;
410
+ return (glyphId) => {
411
+ if (!Number.isInteger(glyphId) || glyphId < 0 || glyphId >= numGlyphs) return null;
412
+ const start = offsets[glyphId];
413
+ const end = offsets[glyphId + 1];
414
+ if (start === void 0 || end === void 0 || end - start < GLYPH_HEADER.size) return null;
415
+ const at = glyf.offset + start;
416
+ if (!fitsInView(view, at, GLYPH_HEADER.size) || start + GLYPH_HEADER.size > glyf.length) return null;
417
+ return {
418
+ xMin: view.getInt16(at + GLYPH_HEADER.xMin),
419
+ yMin: view.getInt16(at + GLYPH_HEADER.yMin),
420
+ xMax: view.getInt16(at + GLYPH_HEADER.xMax),
421
+ yMax: view.getInt16(at + GLYPH_HEADER.yMax)
422
+ };
423
+ };
424
+ };
425
+ /** Requires a table to exist and to be at least `size` bytes long. */
426
+ const requireTable = (directory, tag, size) => {
427
+ const record = directory.tables.get(tag);
428
+ if (!record) return fail(`font has no '${tag}' table`);
429
+ if (record.length < size || !fitsInView(directory.view, record.offset, size)) return fail(`'${tag}' table is ${record.length} bytes, needs at least ${size}`);
430
+ return Result.ok(record);
431
+ };
432
+ const rejectContainer = (version) => {
433
+ switch (version) {
434
+ case SFNT_VERSION.collection: return fail("font collections ('ttcf') are not supported; extract a single face first");
435
+ case SFNT_VERSION.woff: return fail("this is a WOFF container; decode it with decodeWoff before parsing");
436
+ case SFNT_VERSION.woff2: return fail("this is a WOFF2 container; it must be decoded before parsing");
437
+ case SFNT_VERSION.trueType:
438
+ case SFNT_VERSION.trueMac:
439
+ case SFNT_VERSION.openTypeCff: return Result.ok();
440
+ default: return fail(`unrecognized sfnt version 0x${(version >>> 0).toString(16).padStart(8, "0")}`);
441
+ }
442
+ };
443
+ const parseSfnt = (bytes) => {
444
+ const version = readSfntVersion(bytes);
445
+ if (version === void 0) return fail(`buffer is ${bytes.byteLength} bytes, too short to be a font`);
446
+ const container = rejectContainer(version);
447
+ if (container.isErr()) return Result.err(container.error);
448
+ const directoryResult = readSfntDirectory(bytes).mapError((error) => new SfntParseError({ message: error.message }));
449
+ if (directoryResult.isErr()) return Result.err(directoryResult.error);
450
+ const directory = directoryResult.value;
451
+ const { view } = directory;
452
+ const headResult = requireTable(directory, SFNT_TABLE.head, HEAD.size);
453
+ if (headResult.isErr()) return Result.err(headResult.error);
454
+ const head = headResult.value;
455
+ if (view.getUint32(head.offset + HEAD.magicNumber) !== HEAD_MAGIC) return fail("'head' table has the wrong magic number");
456
+ const unitsPerEm = view.getUint16(head.offset + HEAD.unitsPerEm);
457
+ if (unitsPerEm < MIN_UNITS_PER_EM || unitsPerEm > MAX_UNITS_PER_EM) return fail(`head.unitsPerEm is ${unitsPerEm}, outside the permitted 16..16384`);
458
+ const hheaResult = requireTable(directory, SFNT_TABLE.hhea, HHEA.size);
459
+ if (hheaResult.isErr()) return Result.err(hheaResult.error);
460
+ const hhea = hheaResult.value;
461
+ const maxpResult = requireTable(directory, SFNT_TABLE.maxp, MAXP.size);
462
+ if (maxpResult.isErr()) return Result.err(maxpResult.error);
463
+ const numGlyphs = view.getUint16(maxpResult.value.offset + MAXP.numGlyphs);
464
+ if (numGlyphs === 0) return fail("maxp.numGlyphs is 0");
465
+ const numberOfHMetrics = view.getUint16(hhea.offset + HHEA.numberOfHMetrics);
466
+ if (numberOfHMetrics === 0 || numberOfHMetrics > numGlyphs) return fail(`hhea.numberOfHMetrics is ${numberOfHMetrics}, which is not in 1..${numGlyphs} (maxp.numGlyphs)`);
467
+ const hmtxResult = requireTable(directory, SFNT_TABLE.hmtx, numberOfHMetrics * HMTX_METRIC_SIZE);
468
+ if (hmtxResult.isErr()) return Result.err(hmtxResult.error);
469
+ const hmtx = hmtxResult.value;
470
+ const cmap = directory.tables.get(SFNT_TABLE.cmap);
471
+ const unmappedLookup = () => 0;
472
+ const lookupResult = cmap ? buildCmapLookup(view, cmap) : Result.ok(unmappedLookup);
473
+ if (lookupResult.isErr()) return Result.err(lookupResult.error);
474
+ const lookup = lookupResult.value;
475
+ const glyphIdFor = (codePoint) => {
476
+ if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > MAX_UNICODE_CODE_POINT) return 0;
477
+ const glyphId = lookup(codePoint);
478
+ return glyphId < numGlyphs ? glyphId : 0;
479
+ };
480
+ const advanceWidthFor = (glyphId) => {
481
+ if (!Number.isInteger(glyphId) || glyphId < 0) return 0;
482
+ const index = Math.min(glyphId, numberOfHMetrics - 1);
483
+ return view.getUint16(hmtx.offset + index * HMTX_METRIC_SIZE);
484
+ };
485
+ const longLoca = view.getInt16(head.offset + HEAD.indexToLocFormat) === 1;
486
+ const glyphBoundsFor = buildGlyphBoundsReader({
487
+ directory,
488
+ numGlyphs,
489
+ longLoca
490
+ });
491
+ const os2 = directory.tables.get(SFNT_TABLE.os2);
492
+ const os2HasHeights = os2 !== void 0 && os2.length >= OS2.sizeWithHeights && fitsInView(view, os2.offset, OS2.sizeWithHeights) && view.getUint16(os2.offset + OS2.version) >= OS2.minimumVersionWithHeights;
493
+ const capHeight = os2HasHeights ? view.getInt16(os2.offset + OS2.sCapHeight) : glyphBoundsFor(glyphIdFor(CAP_HEIGHT_SAMPLE))?.yMax ?? Math.round(unitsPerEm * CAP_HEIGHT_EM_RATIO);
494
+ const xHeight = os2HasHeights ? view.getInt16(os2.offset + OS2.sxHeight) : glyphBoundsFor(glyphIdFor(X_HEIGHT_SAMPLE))?.yMax ?? Math.round(unitsPerEm * X_HEIGHT_EM_RATIO);
495
+ const fsType = os2 !== void 0 && os2.length >= OS2.fsType + 2 && fitsInView(view, os2.offset, OS2.fsType + 2) ? view.getUint16(os2.offset + OS2.fsType) : 0;
496
+ const post = directory.tables.get(SFNT_TABLE.post);
497
+ const italicAngle = post !== void 0 && post.length >= POST.size && fitsInView(view, post.offset, POST.size) ? view.getInt32(post.offset + POST.italicAngle) / FIXED_POINT_DIVISOR : 0;
498
+ const name = directory.tables.get(SFNT_TABLE.name);
499
+ const postScriptName = (name ? readPostScriptName(view, name) : "") || syntheticPostScriptName(view.getUint32(head.offset + HEAD.checkSumAdjustment));
500
+ return Result.ok({
501
+ unitsPerEm,
502
+ ascender: view.getInt16(hhea.offset + HHEA.ascender),
503
+ descender: view.getInt16(hhea.offset + HHEA.descender),
504
+ lineGap: view.getInt16(hhea.offset + HHEA.lineGap),
505
+ capHeight,
506
+ xHeight,
507
+ italicAngle,
508
+ bbox: [
509
+ view.getInt16(head.offset + HEAD.xMin),
510
+ view.getInt16(head.offset + HEAD.yMin),
511
+ view.getInt16(head.offset + HEAD.xMax),
512
+ view.getInt16(head.offset + HEAD.yMax)
513
+ ],
514
+ postScriptName,
515
+ isCff: directory.tables.has(SFNT_TABLE.cff),
516
+ numGlyphs,
517
+ fsType,
518
+ bytes,
519
+ glyphIdFor,
520
+ advanceWidthFor,
521
+ glyphBoundsFor
522
+ });
523
+ };
524
+ //#endregion
525
+ export { SfntParseError, parseSfnt };
@@ -0,0 +1,20 @@
1
+ import { SfntFont } from "./parse.js";
2
+ import { Result } from "better-result";
3
+ //#region src/fonts/sfnt/subset.d.ts
4
+ declare const SubsetError_base: import("better-result").TaggedErrorClass<"SubsetError">;
5
+ declare class SubsetError extends SubsetError_base<{
6
+ message: string;
7
+ }> {}
8
+ type TrueTypeSubset = {
9
+ /** Subset sfnt bytes. */
10
+ readonly bytes: Uint8Array;
11
+ /** Old glyph id -> new glyph id. Always maps 0 -> 0. */
12
+ readonly glyphIdMap: ReadonlyMap<number, number>;
13
+ };
14
+ /**
15
+ * Build a `glyf`-flavoured subset containing `glyphIds` plus the components
16
+ * they reference transitively, plus .notdef.
17
+ */
18
+ declare const subsetTrueType: (font: SfntFont, glyphIds: ReadonlySet<number>) => Result<TrueTypeSubset, SubsetError>;
19
+ //#endregion
20
+ export { SubsetError, TrueTypeSubset, subsetTrueType };