@bendyline/squisq-formats 2.0.1 → 2.2.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +1 -1
  4. package/dist/{chunk-CRAVSMPZ.js → chunk-26ISNJ7Y.js} +356 -114
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-HTW2M27H.js → chunk-3NKXBZSR.js} +193 -42
  7. package/dist/{chunk-U32AG3G3.js → chunk-4V3KCHAP.js} +3 -4
  8. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  9. package/dist/{chunk-QRVN6A6E.js → chunk-6S6GU3ZG.js} +5 -6
  10. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  11. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  12. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  13. package/dist/{chunk-XKUMNGBW.js → chunk-EJTNGKEA.js} +5 -8
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  16. package/dist/{chunk-ABVI556T.js → chunk-IPN56VLW.js} +83 -58
  17. package/dist/{chunk-LXYLOOST.js → chunk-JE6LSIHE.js} +83 -22
  18. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  19. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  20. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  21. package/dist/chunk-OGS5VCGJ.js +446 -0
  22. package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
  23. package/dist/{chunk-2KPARF2P.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-VSYHZECT.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-WC7WULGV.js → chunk-X2DEAXNK.js} +62 -2
  27. package/dist/container/index.js +1 -2
  28. package/dist/csv/index.d.ts +27 -2
  29. package/dist/csv/index.js +1 -2
  30. package/dist/docx/index.d.ts +5 -1
  31. package/dist/docx/index.js +9 -10
  32. package/dist/epub/index.d.ts +2 -0
  33. package/dist/epub/index.js +5 -6
  34. package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
  35. package/dist/extract-MN7LA3NL.js +13 -0
  36. package/dist/html/index.d.ts +11 -4
  37. package/dist/html/index.js +3 -4
  38. package/dist/images-ESPQKVTW.js +6 -0
  39. package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
  40. package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -26
  43. package/dist/infer/index.d.ts +3 -3
  44. package/dist/infer/index.js +7 -9
  45. package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
  46. package/dist/layouts-DRWZGSPD.js +10 -0
  47. package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
  48. package/dist/ooxml/index.d.ts +3 -3
  49. package/dist/ooxml/index.js +14 -13
  50. package/dist/pdf/index.d.ts +18 -0
  51. package/dist/pdf/index.js +2 -3
  52. package/dist/pptx/index.d.ts +4 -4
  53. package/dist/pptx/index.js +11 -13
  54. package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
  55. package/dist/registry/index.d.ts +21 -5
  56. package/dist/registry/index.js +9 -6
  57. package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
  58. package/dist/xlsx/index.d.ts +3 -3
  59. package/dist/xlsx/index.js +6 -7
  60. package/package.json +6 -3
  61. package/dist/chunk-2KPARF2P.js.map +0 -1
  62. package/dist/chunk-4VUWTSGM.js.map +0 -1
  63. package/dist/chunk-6M7Z25LA.js +0 -46
  64. package/dist/chunk-6M7Z25LA.js.map +0 -1
  65. package/dist/chunk-ABVI556T.js.map +0 -1
  66. package/dist/chunk-CRAVSMPZ.js.map +0 -1
  67. package/dist/chunk-FE6OJV6O.js.map +0 -1
  68. package/dist/chunk-GVS2XXV6.js.map +0 -1
  69. package/dist/chunk-HTW2M27H.js.map +0 -1
  70. package/dist/chunk-LXYLOOST.js.map +0 -1
  71. package/dist/chunk-MLX2BOJC.js.map +0 -1
  72. package/dist/chunk-NKAJPJ4G.js.map +0 -1
  73. package/dist/chunk-O3GVVND4.js.map +0 -1
  74. package/dist/chunk-ODL3SSPT.js.map +0 -1
  75. package/dist/chunk-PN52A5AA.js.map +0 -1
  76. package/dist/chunk-QRVN6A6E.js.map +0 -1
  77. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  78. package/dist/chunk-SSUPBUF5.js.map +0 -1
  79. package/dist/chunk-U32AG3G3.js.map +0 -1
  80. package/dist/chunk-U4MRIFKL.js.map +0 -1
  81. package/dist/chunk-VJJM2SSH.js +0 -275
  82. package/dist/chunk-VJJM2SSH.js.map +0 -1
  83. package/dist/chunk-VSYHZECT.js.map +0 -1
  84. package/dist/chunk-WC7WULGV.js.map +0 -1
  85. package/dist/chunk-XKUMNGBW.js.map +0 -1
  86. package/dist/chunk-YRT7GQ5Y.js +0 -28
  87. package/dist/chunk-YRT7GQ5Y.js.map +0 -1
  88. package/dist/container/index.js.map +0 -1
  89. package/dist/csv/index.js.map +0 -1
  90. package/dist/docx/index.js.map +0 -1
  91. package/dist/epub/index.js.map +0 -1
  92. package/dist/extract-H6RXJMHP.js +0 -15
  93. package/dist/extract-H6RXJMHP.js.map +0 -1
  94. package/dist/html/index.js.map +0 -1
  95. package/dist/images-7FBWPKE3.js +0 -7
  96. package/dist/images-7FBWPKE3.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/infer/index.js.map +0 -1
  99. package/dist/layouts-QVPK3ZCU.js +0 -12
  100. package/dist/layouts-QVPK3ZCU.js.map +0 -1
  101. package/dist/mapTheme-IR27S6IV.js.map +0 -1
  102. package/dist/ooxml/index.js.map +0 -1
  103. package/dist/pdf/index.js.map +0 -1
  104. package/dist/pptx/index.js.map +0 -1
  105. package/dist/registry/index.js.map +0 -1
  106. package/dist/xlsx/index.js.map +0 -1
  107. package/src/__tests__/container.test.ts +0 -230
  108. package/src/__tests__/convert.test.ts +0 -495
  109. package/src/__tests__/csvImport.test.ts +0 -84
  110. package/src/__tests__/docxExport.test.ts +0 -457
  111. package/src/__tests__/docxImport.test.ts +0 -410
  112. package/src/__tests__/epub.test.ts +0 -649
  113. package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
  114. package/src/__tests__/formatRegistry.test.ts +0 -174
  115. package/src/__tests__/html.test.ts +0 -435
  116. package/src/__tests__/htmlImport.test.ts +0 -57
  117. package/src/__tests__/inferTheme.test.ts +0 -135
  118. package/src/__tests__/lossyWarnings.test.ts +0 -146
  119. package/src/__tests__/ooxml.test.ts +0 -271
  120. package/src/__tests__/ooxmlCancellation.test.ts +0 -113
  121. package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
  122. package/src/__tests__/pdfExport.test.ts +0 -322
  123. package/src/__tests__/pdfImport.test.ts +0 -384
  124. package/src/__tests__/plainHtml.test.ts +0 -417
  125. package/src/__tests__/plainHtmlBundle.test.ts +0 -253
  126. package/src/__tests__/pptxExport.test.ts +0 -138
  127. package/src/__tests__/pptxImport.test.ts +0 -145
  128. package/src/__tests__/pptxInferFixtures.ts +0 -314
  129. package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
  130. package/src/__tests__/roundTrip.test.ts +0 -201
  131. package/src/__tests__/roundTripAssets.test.ts +0 -50
  132. package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
  133. package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
  134. package/src/__tests__/roundTripMatrix.test.ts +0 -142
  135. package/src/__tests__/sharedContainer.test.ts +0 -41
  136. package/src/__tests__/sharedImages.test.ts +0 -61
  137. package/src/__tests__/xlsxExport.test.ts +0 -164
  138. package/src/__tests__/xlsxImport.test.ts +0 -80
  139. package/src/__tests__/zipSafety.test.ts +0 -317
  140. package/src/container/index.ts +0 -94
  141. package/src/csv/index.ts +0 -188
  142. package/src/docx/export.ts +0 -1267
  143. package/src/docx/import.ts +0 -995
  144. package/src/docx/index.ts +0 -26
  145. package/src/docx/styles.ts +0 -145
  146. package/src/epub/export.ts +0 -968
  147. package/src/epub/index.ts +0 -20
  148. package/src/html/docsHtmlBundle.ts +0 -373
  149. package/src/html/htmlTemplate.ts +0 -385
  150. package/src/html/imageUtils.ts +0 -61
  151. package/src/html/import.ts +0 -297
  152. package/src/html/index.ts +0 -212
  153. package/src/html/plainHtml.ts +0 -790
  154. package/src/html/plainHtmlBundle.ts +0 -421
  155. package/src/index.ts +0 -109
  156. package/src/infer/extract.ts +0 -127
  157. package/src/infer/index.ts +0 -199
  158. package/src/infer/mapTheme.ts +0 -176
  159. package/src/infer/types.ts +0 -27
  160. package/src/ooxml/index.ts +0 -111
  161. package/src/ooxml/namespaces.ts +0 -196
  162. package/src/ooxml/readUtils.ts +0 -44
  163. package/src/ooxml/reader.ts +0 -318
  164. package/src/ooxml/themeReader.ts +0 -197
  165. package/src/ooxml/types.ts +0 -103
  166. package/src/ooxml/writer.ts +0 -339
  167. package/src/ooxml/xmlUtils.ts +0 -123
  168. package/src/pdf/export.ts +0 -1084
  169. package/src/pdf/import.ts +0 -1164
  170. package/src/pdf/index.ts +0 -29
  171. package/src/pdf/styles.ts +0 -180
  172. package/src/pptx/export.ts +0 -1184
  173. package/src/pptx/import.ts +0 -455
  174. package/src/pptx/index.ts +0 -52
  175. package/src/pptx/layouts.ts +0 -1222
  176. package/src/pptx/styles.ts +0 -96
  177. package/src/pptx/templates.ts +0 -187
  178. package/src/registry/convert.ts +0 -433
  179. package/src/registry/defaultFormats.ts +0 -413
  180. package/src/registry/errors.ts +0 -46
  181. package/src/registry/index.ts +0 -43
  182. package/src/registry/registry.ts +0 -48
  183. package/src/registry/types.ts +0 -170
  184. package/src/shared/boundedZipArchive.ts +0 -383
  185. package/src/shared/container.ts +0 -28
  186. package/src/shared/fidelity.ts +0 -130
  187. package/src/shared/images.ts +0 -44
  188. package/src/shared/inlineRuns.ts +0 -99
  189. package/src/shared/text.ts +0 -41
  190. package/src/shared/zipEntryCount.ts +0 -151
  191. package/src/shared/zipLimits.ts +0 -296
  192. package/src/shared/zipSafety.ts +0 -19
  193. package/src/xlsx/export.ts +0 -253
  194. package/src/xlsx/import.ts +0 -160
  195. package/src/xlsx/index.ts +0 -35
@@ -1,11 +1,314 @@
1
1
  import {
2
2
  buildContainer
3
- } from "./chunk-SSUPBUF5.js";
3
+ } from "./chunk-IIQYS2YH.js";
4
4
 
5
5
  // src/pdf/export.ts
6
- import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
6
+ import { PDFDocument, StandardFonts, rgb, PDFString } from "pdf-lib";
7
7
  import { docToMarkdown, resolveThemeForDoc } from "@bendyline/squisq/doc";
8
- import { readFrontmatterThemeId } from "@bendyline/squisq/markdown";
8
+ import { readFrontmatterThemeId, sanitizeUrl } from "@bendyline/squisq/markdown";
9
+
10
+ // src/pdf/winAnsi.ts
11
+ var CP1252_HIGH_SPECIALS = /* @__PURE__ */ new Set([
12
+ 8364,
13
+ // euro sign
14
+ 8218,
15
+ // single low-9 quotation mark
16
+ 402,
17
+ // latin small letter f with hook
18
+ 8222,
19
+ // double low-9 quotation mark
20
+ 8230,
21
+ // horizontal ellipsis
22
+ 8224,
23
+ // dagger
24
+ 8225,
25
+ // double dagger
26
+ 710,
27
+ // modifier letter circumflex accent
28
+ 8240,
29
+ // per mille sign
30
+ 352,
31
+ // latin capital letter S with caron
32
+ 8249,
33
+ // single left-pointing angle quotation mark
34
+ 338,
35
+ // latin capital ligature OE
36
+ 381,
37
+ // latin capital letter Z with caron
38
+ 8216,
39
+ // left single quotation mark
40
+ 8217,
41
+ // right single quotation mark
42
+ 8220,
43
+ // left double quotation mark
44
+ 8221,
45
+ // right double quotation mark
46
+ 8226,
47
+ // bullet
48
+ 8211,
49
+ // en dash
50
+ 8212,
51
+ // em dash
52
+ 732,
53
+ // small tilde
54
+ 8482,
55
+ // trade mark sign
56
+ 353,
57
+ // latin small letter s with caron
58
+ 8250,
59
+ // single right-pointing angle quotation mark
60
+ 339,
61
+ // latin small ligature oe
62
+ 382,
63
+ // latin small letter z with caron
64
+ 376
65
+ // latin capital letter Y with diaeresis
66
+ ]);
67
+ function isWinAnsiEncodable(codePoint) {
68
+ if (codePoint >= 32 && codePoint <= 126) return true;
69
+ if (codePoint >= 160 && codePoint <= 255) return true;
70
+ return CP1252_HIGH_SPECIALS.has(codePoint);
71
+ }
72
+ var WINANSI_REPLACEMENT = "?";
73
+ var TRANSLITERATIONS = /* @__PURE__ */ new Map([
74
+ // ── Whitespace & invisibles ──
75
+ // A tab keeps its visual width (code fences are the common case); every
76
+ // other control/format character collapses to a space or disappears.
77
+ [9, " "],
78
+ // tab
79
+ [10, " "],
80
+ // line feed
81
+ [11, " "],
82
+ // vertical tab
83
+ [12, " "],
84
+ // form feed
85
+ [13, " "],
86
+ // carriage return
87
+ [133, " "],
88
+ // next line
89
+ [8232, " "],
90
+ // line separator
91
+ [8233, " "],
92
+ // paragraph separator
93
+ [8192, " "],
94
+ // en quad
95
+ [8193, " "],
96
+ // em quad
97
+ [8194, " "],
98
+ // en space
99
+ [8195, " "],
100
+ // em space
101
+ [8196, " "],
102
+ // three-per-em space
103
+ [8197, " "],
104
+ // four-per-em space
105
+ [8198, " "],
106
+ // six-per-em space
107
+ [8199, " "],
108
+ // figure space
109
+ [8200, " "],
110
+ // punctuation space
111
+ [8201, " "],
112
+ // thin space
113
+ [8202, " "],
114
+ // hair space
115
+ [8239, " "],
116
+ // narrow no-break space
117
+ [8287, " "],
118
+ // medium mathematical space
119
+ [12288, " "],
120
+ // ideographic space
121
+ [8203, ""],
122
+ // zero-width space
123
+ [8204, ""],
124
+ // zero-width non-joiner
125
+ [8205, ""],
126
+ // zero-width joiner
127
+ [8288, ""],
128
+ // word joiner
129
+ [65279, ""],
130
+ // zero-width no-break space / BOM
131
+ // ── Dashes & hyphens (en/em dash ARE encodable and stay verbatim) ──
132
+ [8208, "-"],
133
+ // hyphen
134
+ [8209, "-"],
135
+ // non-breaking hyphen
136
+ [8210, "-"],
137
+ // figure dash
138
+ [8213, "--"],
139
+ // horizontal bar
140
+ [8722, "-"],
141
+ // minus sign
142
+ // ── Quotes & primes (curly quotes ARE encodable and stay verbatim) ──
143
+ [8242, "'"],
144
+ // prime
145
+ [8243, '"'],
146
+ // double prime
147
+ [8245, "'"],
148
+ // reversed prime
149
+ [8246, '"'],
150
+ // reversed double prime
151
+ // ── Arrows ──
152
+ [8592, "<-"],
153
+ // leftwards arrow
154
+ [8593, "^"],
155
+ // upwards arrow
156
+ [8594, "->"],
157
+ // rightwards arrow
158
+ [8595, "v"],
159
+ // downwards arrow
160
+ [8596, "<->"],
161
+ // left right arrow
162
+ [8656, "<="],
163
+ // leftwards double arrow
164
+ [8658, "=>"],
165
+ // rightwards double arrow
166
+ [8660, "<=>"],
167
+ // left right double arrow
168
+ // ── Math & comparison ──
169
+ [8800, "!="],
170
+ // not equal to
171
+ [8804, "<="],
172
+ // less-than or equal to
173
+ [8805, ">="],
174
+ // greater-than or equal to
175
+ [8776, "~="],
176
+ // almost equal to
177
+ [8801, "=="],
178
+ // identical to
179
+ [8734, "inf"],
180
+ // infinity
181
+ [8730, "sqrt"],
182
+ // square root
183
+ [8721, "sum"],
184
+ // n-ary summation
185
+ [8719, "prod"],
186
+ // n-ary product
187
+ [8260, "/"],
188
+ // fraction slash
189
+ // ── Bullets & marks (U+2022 bullet IS encodable) ──
190
+ [8227, "*"],
191
+ // triangular bullet
192
+ [8259, "-"],
193
+ // hyphen bullet
194
+ [8729, "*"],
195
+ // bullet operator
196
+ [9642, "*"],
197
+ // black small square
198
+ [9643, "*"],
199
+ // white small square
200
+ [9675, "o"],
201
+ // white circle
202
+ [9679, "*"],
203
+ // black circle
204
+ [9702, "o"],
205
+ // white bullet
206
+ [10003, "v"],
207
+ // check mark
208
+ [10004, "v"],
209
+ // heavy check mark
210
+ [10007, "x"],
211
+ // ballot x
212
+ [10008, "x"],
213
+ // heavy ballot x
214
+ // ── Ellipsis variants (U+2026 itself IS encodable) ──
215
+ [8229, ".."],
216
+ // two dot leader
217
+ [8943, "..."],
218
+ // midline horizontal ellipsis
219
+ // ── Ligatures ──
220
+ [64256, "ff"],
221
+ [64257, "fi"],
222
+ [64258, "fl"],
223
+ [64259, "ffi"],
224
+ [64260, "ffl"]
225
+ ]);
226
+ var WHITESPACE_LIKE = /* @__PURE__ */ new Set([
227
+ 9,
228
+ 10,
229
+ 11,
230
+ 12,
231
+ 13,
232
+ 133,
233
+ 8232,
234
+ 8233,
235
+ 8192,
236
+ 8193,
237
+ 8194,
238
+ 8195,
239
+ 8196,
240
+ 8197,
241
+ 8198,
242
+ 8199,
243
+ 8200,
244
+ 8201,
245
+ 8202,
246
+ 8239,
247
+ 8287,
248
+ 12288,
249
+ 8203,
250
+ 8204,
251
+ 8205,
252
+ 8288,
253
+ 65279
254
+ ]);
255
+ var MAX_SAMPLES = 5;
256
+ var ASCII_ONLY = /^[\x20-\x7e]*$/;
257
+ var COMBINING_MARKS = /[̀-ͯ]/g;
258
+ function stripDiacritics(ch) {
259
+ const decomposed = ch.normalize("NFKD").replace(COMBINING_MARKS, "");
260
+ if (!decomposed) return void 0;
261
+ for (const c of decomposed) {
262
+ if (!isWinAnsiEncodable(c.codePointAt(0))) return void 0;
263
+ }
264
+ return decomposed;
265
+ }
266
+ function sanitizeWinAnsi(input) {
267
+ if (ASCII_ONLY.test(input)) return { text: input, substitutions: 0, samples: [] };
268
+ const text = input.normalize("NFC");
269
+ let out = "";
270
+ let substitutions = 0;
271
+ const samples = [];
272
+ for (const ch of text) {
273
+ const cp = ch.codePointAt(0);
274
+ if (isWinAnsiEncodable(cp)) {
275
+ out += ch;
276
+ continue;
277
+ }
278
+ const mapped = TRANSLITERATIONS.get(cp) ?? stripDiacritics(ch) ?? WINANSI_REPLACEMENT;
279
+ out += mapped;
280
+ if (WHITESPACE_LIKE.has(cp)) continue;
281
+ substitutions++;
282
+ const sample = `"${ch}" -> "${mapped}"`;
283
+ if (samples.length < MAX_SAMPLES && !samples.includes(sample)) samples.push(sample);
284
+ }
285
+ return { text: out, substitutions, samples };
286
+ }
287
+ var WinAnsiTracker = class {
288
+ constructor() {
289
+ this.count = 0;
290
+ this.samples = [];
291
+ }
292
+ /** Sanitize `text` for pdf-lib and record any substitutions. */
293
+ clean(text) {
294
+ const result = sanitizeWinAnsi(text);
295
+ this.count += result.substitutions;
296
+ for (const sample of result.samples) {
297
+ if (this.samples.length >= MAX_SAMPLES) break;
298
+ if (!this.samples.includes(sample)) this.samples.push(sample);
299
+ }
300
+ return result.text;
301
+ }
302
+ /** Total substituted characters across the export. */
303
+ get substitutions() {
304
+ return this.count;
305
+ }
306
+ /** One-shot summary for the export, or undefined when nothing was lost. */
307
+ warning() {
308
+ if (this.count === 0) return void 0;
309
+ return `PDF export replaced ${this.count} character(s) that the standard PDF fonts cannot encode (${this.samples.join(", ")}). Scripts such as emoji, CJK and Cyrillic require an embedded Unicode font.`;
310
+ }
311
+ };
9
312
 
10
313
  // src/pdf/styles.ts
11
314
  var PAGE_WIDTH_LETTER = 612;
@@ -72,6 +375,7 @@ var IMPORT_URL_PATTERN = /https?:\/\/[^\s)>\]]+/g;
72
375
 
73
376
  // src/pdf/export.ts
74
377
  async function markdownDocToPdf(doc, options = {}) {
378
+ options.signal?.throwIfAborted();
75
379
  const pdfDoc = await PDFDocument.create();
76
380
  if (options.title) pdfDoc.setTitle(options.title);
77
381
  if (options.author) pdfDoc.setAuthor(options.author);
@@ -79,6 +383,11 @@ async function markdownDocToPdf(doc, options = {}) {
79
383
  pdfDoc.setModificationDate(/* @__PURE__ */ new Date());
80
384
  const ctx = await createExportContext(pdfDoc, options, doc);
81
385
  renderBlocks(doc.children, ctx, 0);
386
+ const warning = ctx.text.warning();
387
+ if (warning) {
388
+ if (options.onWarning) options.onWarning(warning);
389
+ else console.warn(warning);
390
+ }
82
391
  const bytes = await pdfDoc.save();
83
392
  return bytes.buffer;
84
393
  }
@@ -124,7 +433,9 @@ async function createExportContext(pdfDoc, options, doc) {
124
433
  y: pageHeight - margin,
125
434
  contentWidth: pageWidth - 2 * margin,
126
435
  bottomY: margin,
127
- colors: { text: colorText, heading: colorHeading, link: colorLink }
436
+ colors: { text: colorText, heading: colorHeading, link: colorLink },
437
+ text: new WinAnsiTracker(),
438
+ signal: options.signal
128
439
  };
129
440
  }
130
441
  function ensureSpace(ctx, needed) {
@@ -144,7 +455,7 @@ function flattenInlines(nodes, ctx, state) {
144
455
  case "text": {
145
456
  const font = state.code ? state.bold ? ctx.fonts.monoBold : ctx.fonts.mono : pickFont(ctx, state.bold, state.italic);
146
457
  spans.push({
147
- text: node.value,
458
+ text: ctx.text.clean(node.value),
148
459
  font,
149
460
  fontSize: state.code ? CODE_FONT_SIZE : ctx.fontSize,
150
461
  color: state.code ? COLOR_CODE_TEXT : state.color ?? ctx.colors.text,
@@ -173,7 +484,7 @@ function flattenInlines(nodes, ctx, state) {
173
484
  break;
174
485
  case "inlineCode": {
175
486
  spans.push({
176
- text: node.value,
487
+ text: ctx.text.clean(node.value),
177
488
  font: ctx.fonts.mono,
178
489
  fontSize: CODE_FONT_SIZE,
179
490
  color: COLOR_CODE_TEXT,
@@ -183,11 +494,12 @@ function flattenInlines(nodes, ctx, state) {
183
494
  }
184
495
  case "link": {
185
496
  const linkNode = node;
497
+ const safeLink = sanitizeUrl(linkNode.url, "link") ?? void 0;
186
498
  spans.push(
187
499
  ...flattenInlines(linkNode.children, ctx, {
188
500
  ...state,
189
- link: linkNode.url,
190
- color: ctx.colors.link
501
+ link: safeLink,
502
+ color: safeLink ? ctx.colors.link : state.color
191
503
  })
192
504
  );
193
505
  break;
@@ -195,7 +507,7 @@ function flattenInlines(nodes, ctx, state) {
195
507
  case "image": {
196
508
  const imgNode = node;
197
509
  spans.push({
198
- text: imgNode.alt ? `[Image: ${imgNode.alt}]` : "[Image]",
510
+ text: ctx.text.clean(imgNode.alt ? `[Image: ${imgNode.alt}]` : "[Image]"),
199
511
  font: ctx.fonts.italic,
200
512
  fontSize: ctx.fontSize,
201
513
  color: COLOR_BLOCKQUOTE_TEXT
@@ -214,7 +526,7 @@ function flattenInlines(nodes, ctx, state) {
214
526
  const html = node.rawHtml;
215
527
  if (html) {
216
528
  spans.push({
217
- text: html,
529
+ text: ctx.text.clean(html),
218
530
  font: ctx.fonts.mono,
219
531
  fontSize: CODE_FONT_SIZE,
220
532
  color: COLOR_CODE_TEXT
@@ -224,7 +536,7 @@ function flattenInlines(nodes, ctx, state) {
224
536
  }
225
537
  case "inlineMath":
226
538
  spans.push({
227
- text: node.value,
539
+ text: ctx.text.clean(node.value),
228
540
  font: ctx.fonts.mono,
229
541
  fontSize: CODE_FONT_SIZE,
230
542
  color: COLOR_CODE_TEXT
@@ -233,7 +545,7 @@ function flattenInlines(nodes, ctx, state) {
233
545
  case "footnoteReference": {
234
546
  const ref = node;
235
547
  spans.push({
236
- text: `[${ref.identifier}]`,
548
+ text: ctx.text.clean(`[${ref.identifier}]`),
237
549
  font: ctx.fonts.regular,
238
550
  fontSize: ctx.fontSize * 0.75,
239
551
  color: ctx.colors.link
@@ -247,7 +559,7 @@ function flattenInlines(nodes, ctx, state) {
247
559
  spans.push(...flattenInlines(fallback.children, ctx, state));
248
560
  } else if (fallback.value) {
249
561
  spans.push({
250
- text: fallback.value,
562
+ text: ctx.text.clean(fallback.value),
251
563
  font: pickFont(ctx, state.bold, state.italic),
252
564
  fontSize: ctx.fontSize,
253
565
  color: state.color ?? ctx.colors.text
@@ -283,12 +595,27 @@ function drawSpans(spans, ctx, availableWidth, x0) {
283
595
  });
284
596
  const textWidth = span.font.widthOfTextAtSize(span.text, span.fontSize);
285
597
  if (span.link) {
598
+ const baseline = ctx.y - span.fontSize;
286
599
  ctx.page.drawLine({
287
- start: { x, y: ctx.y - span.fontSize - 1 },
288
- end: { x: x + textWidth, y: ctx.y - span.fontSize - 1 },
600
+ start: { x, y: baseline - 1 },
601
+ end: { x: x + textWidth, y: baseline - 1 },
289
602
  thickness: 0.5,
290
603
  color: rgb(span.color.r, span.color.g, span.color.b)
291
604
  });
605
+ const annotation = ctx.pdfDoc.context.register(
606
+ ctx.pdfDoc.context.obj({
607
+ Type: "Annot",
608
+ Subtype: "Link",
609
+ Rect: [x, baseline - 2, x + textWidth, baseline + span.fontSize + 2],
610
+ Border: [0, 0, 0],
611
+ A: {
612
+ Type: "Action",
613
+ S: "URI",
614
+ URI: PDFString.of(span.link)
615
+ }
616
+ })
617
+ );
618
+ ctx.page.node.addAnnot(annotation);
292
619
  }
293
620
  if (span.strikethrough) {
294
621
  const midY = ctx.y - span.fontSize * 0.6;
@@ -360,7 +687,9 @@ function getLineHeight(line) {
360
687
  return (maxSize || DEFAULT_FONT_SIZE) * LINE_HEIGHT_FACTOR;
361
688
  }
362
689
  function renderBlocks(nodes, ctx, extraIndent) {
363
- for (const node of nodes) {
690
+ for (let index = 0; index < nodes.length; index++) {
691
+ if ((index & 255) === 0) ctx.signal?.throwIfAborted();
692
+ const node = nodes[index];
364
693
  renderBlock(node, ctx, extraIndent);
365
694
  }
366
695
  }
@@ -499,7 +828,7 @@ function renderListItem(item, ctx, extraIndent, depth, ordered, counter) {
499
828
  function renderCodeBlock(node, ctx, extraIndent) {
500
829
  const x0 = ctx.margin + extraIndent;
501
830
  const _w = ctx.contentWidth - extraIndent;
502
- const lines = node.value.split("\n");
831
+ const lines = node.value.split("\n").map((line) => ctx.text.clean(line));
503
832
  const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;
504
833
  const totalHeight = lines.length * lineH + 12;
505
834
  ensureSpace(ctx, Math.min(totalHeight, ctx.y - ctx.bottomY));
@@ -615,7 +944,7 @@ function renderThematicBreak(ctx, extraIndent) {
615
944
  }
616
945
  function renderHtmlBlock(node, ctx, extraIndent) {
617
946
  if (!node.rawHtml) return;
618
- const lines = node.rawHtml.split("\n");
947
+ const lines = node.rawHtml.split("\n").map((line) => ctx.text.clean(line));
619
948
  const x0 = ctx.margin + extraIndent;
620
949
  for (const line of lines) {
621
950
  if (line.trim().length === 0) continue;
@@ -633,7 +962,7 @@ function renderHtmlBlock(node, ctx, extraIndent) {
633
962
  ctx.y -= PARAGRAPH_SPACING;
634
963
  }
635
964
  function renderMathBlock(node, ctx, extraIndent) {
636
- const lines = node.value.split("\n");
965
+ const lines = node.value.split("\n").map((line) => ctx.text.clean(line));
637
966
  const x0 = ctx.margin + extraIndent;
638
967
  for (const line of lines) {
639
968
  const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;
@@ -652,7 +981,7 @@ function renderMathBlock(node, ctx, extraIndent) {
652
981
  ctx.y -= PARAGRAPH_SPACING;
653
982
  }
654
983
  function renderFootnoteDefinition(node, ctx, extraIndent) {
655
- const label = `[${node.identifier}]`;
984
+ const label = ctx.text.clean(`[${node.identifier}]`);
656
985
  const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;
657
986
  ensureSpace(ctx, lineH);
658
987
  ctx.page.drawText(label, {
@@ -694,7 +1023,7 @@ function renderFallbackBlock(node, ctx, extraIndent) {
694
1023
  } else if (fallback.value && typeof fallback.value === "string") {
695
1024
  const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;
696
1025
  ensureSpace(ctx, lineH);
697
- ctx.page.drawText(fallback.value, {
1026
+ ctx.page.drawText(ctx.text.clean(fallback.value), {
698
1027
  x: ctx.margin + extraIndent,
699
1028
  y: ctx.y - ctx.fontSize,
700
1029
  size: ctx.fontSize,
@@ -717,11 +1046,13 @@ function hexToRgb(hex) {
717
1046
  // src/pdf/import.ts
718
1047
  import { markdownToDoc } from "@bendyline/squisq/doc";
719
1048
  import { stringifyMarkdown } from "@bendyline/squisq/markdown";
1049
+ import UPNG from "@pdf-lib/upng";
720
1050
  async function pdfToMarkdownDoc(data, options = {}) {
1051
+ options.signal?.throwIfAborted();
721
1052
  const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
722
- const loaded = await loadPdfDocument(bytes);
1053
+ const loaded = await loadPdfDocument(bytes, options.signal);
723
1054
  try {
724
- const textLines = await extractTextLines(loaded.pdf);
1055
+ const textLines = await extractTextLines(loaded.pdf, options);
725
1056
  if (textLines.length === 0) {
726
1057
  return { type: "document", children: [] };
727
1058
  }
@@ -737,12 +1068,16 @@ async function pdfToDoc(data, options = {}) {
737
1068
  return markdownToDoc(markdownDoc);
738
1069
  }
739
1070
  async function pdfToContainer(data, options = {}) {
1071
+ options.signal?.throwIfAborted();
740
1072
  const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
741
- const loaded = await loadPdfDocument(bytes);
1073
+ const loaded = await loadPdfDocument(bytes, options.signal);
742
1074
  let textLines;
743
1075
  let images;
744
1076
  try {
745
- [textLines, images] = await Promise.all([extractTextLines(loaded.pdf), extractImages(loaded)]);
1077
+ [textLines, images] = await Promise.all([
1078
+ extractTextLines(loaded.pdf, options),
1079
+ extractImages(loaded, options)
1080
+ ]);
746
1081
  } finally {
747
1082
  await loaded.pdf.destroy?.();
748
1083
  }
@@ -758,25 +1093,48 @@ async function pdfToContainer(data, options = {}) {
758
1093
  images.map((img) => [img.path, { data: img.data, mimeType: "image/png" }])
759
1094
  );
760
1095
  }
761
- async function extractImages(loaded) {
762
- if (typeof document === "undefined") return [];
1096
+ async function extractImages(loaded, options) {
763
1097
  const { pdfjsLib, pdf } = loaded;
764
1098
  const OPS_paintImageXObject = pdfjsLib.OPS?.paintImageXObject ?? 85;
1099
+ const OPS_paintInlineImageXObject = pdfjsLib.OPS?.paintInlineImageXObject ?? 86;
765
1100
  const images = [];
766
1101
  let counter = 0;
1102
+ let totalPixels = 0;
1103
+ const maxPages = safetyLimit("maxPages", options.maxPages ?? 5e3);
1104
+ const maxImagePixels = safetyLimit("maxImagePixels", options.maxImagePixels ?? 1e8);
1105
+ if (pdf.numPages > maxPages)
1106
+ throw new RangeError(`PDF exceeds the ${maxPages}-page safety limit`);
767
1107
  for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
1108
+ options.signal?.throwIfAborted();
768
1109
  const page = await pdf.getPage(pageNum);
769
1110
  if (!page.getOperatorList) continue;
770
1111
  const opList = await page.getOperatorList();
771
1112
  const seen = /* @__PURE__ */ new Set();
772
1113
  for (let i = 0; i < opList.fnArray.length; i++) {
773
- if (opList.fnArray[i] !== OPS_paintImageXObject) continue;
774
- const imgName = opList.argsArray[i]?.[0];
775
- if (!imgName || typeof imgName !== "string" || seen.has(imgName)) continue;
776
- seen.add(imgName);
1114
+ const operation = opList.fnArray[i];
1115
+ if (operation !== OPS_paintImageXObject && operation !== OPS_paintInlineImageXObject)
1116
+ continue;
1117
+ const operand = opList.argsArray[i]?.[0];
1118
+ let imgData = null;
1119
+ if (operation === OPS_paintImageXObject) {
1120
+ if (!operand || typeof operand !== "string" || seen.has(operand)) continue;
1121
+ seen.add(operand);
1122
+ try {
1123
+ imgData = await getPdfImageObject(page.objs, operand, options.signal);
1124
+ } catch {
1125
+ continue;
1126
+ }
1127
+ } else if (operand && typeof operand === "object") {
1128
+ imgData = operand;
1129
+ }
777
1130
  try {
778
- const imgData = page.objs?.get(imgName);
779
1131
  if (!imgData?.data || !imgData.width || !imgData.height) continue;
1132
+ totalPixels += imgData.width * imgData.height;
1133
+ if (totalPixels > maxImagePixels) {
1134
+ throw new RangeError(
1135
+ `PDF decoded images exceed the ${maxImagePixels}-pixel safety limit`
1136
+ );
1137
+ }
780
1138
  const pngData = imageDataToPng(imgData);
781
1139
  if (!pngData) continue;
782
1140
  counter++;
@@ -786,52 +1144,69 @@ async function extractImages(loaded) {
786
1144
  page: pageNum - 1,
787
1145
  y: 0
788
1146
  });
789
- } catch {
1147
+ } catch (error) {
1148
+ if (error instanceof RangeError) throw error;
790
1149
  }
791
1150
  }
792
1151
  }
793
1152
  return images;
794
1153
  }
1154
+ function getPdfImageObject(objects, name, signal) {
1155
+ if (!objects) return Promise.resolve(null);
1156
+ return new Promise((resolve, reject) => {
1157
+ let settled = false;
1158
+ const finish = (value) => {
1159
+ if (settled) return;
1160
+ settled = true;
1161
+ signal?.removeEventListener("abort", onAbort);
1162
+ resolve(value);
1163
+ };
1164
+ const onAbort = () => {
1165
+ if (settled) return;
1166
+ settled = true;
1167
+ reject(signal?.reason ?? new DOMException("PDF image extraction cancelled", "AbortError"));
1168
+ };
1169
+ if (signal?.aborted) {
1170
+ onAbort();
1171
+ return;
1172
+ }
1173
+ signal?.addEventListener("abort", onAbort, { once: true });
1174
+ try {
1175
+ const immediate = objects.get(name, finish);
1176
+ if (immediate !== void 0 && immediate !== null) finish(immediate);
1177
+ } catch (error) {
1178
+ signal?.removeEventListener("abort", onAbort);
1179
+ reject(error);
1180
+ }
1181
+ });
1182
+ }
795
1183
  function imageDataToPng(img) {
796
1184
  try {
797
- const canvas = document.createElement("canvas");
798
- canvas.width = img.width;
799
- canvas.height = img.height;
800
- const ctx = canvas.getContext("2d");
801
- if (!ctx) return null;
802
- let imageData;
1185
+ let rgba;
803
1186
  if (img.kind === 3 || img.data.length === img.width * img.height * 4) {
804
- imageData = new ImageData(new Uint8ClampedArray(img.data), img.width, img.height);
1187
+ rgba = new Uint8ClampedArray(img.data);
805
1188
  } else if (img.kind === 2 || img.data.length === img.width * img.height * 3) {
806
- const rgba = new Uint8ClampedArray(img.width * img.height * 4);
1189
+ rgba = new Uint8ClampedArray(img.width * img.height * 4);
807
1190
  for (let j = 0, k = 0; j < img.data.length; j += 3, k += 4) {
808
1191
  rgba[k] = img.data[j];
809
1192
  rgba[k + 1] = img.data[j + 1];
810
1193
  rgba[k + 2] = img.data[j + 2];
811
1194
  rgba[k + 3] = 255;
812
1195
  }
813
- imageData = new ImageData(rgba, img.width, img.height);
814
1196
  } else if (img.kind === 1 || img.data.length === img.width * img.height) {
815
- const rgba = new Uint8ClampedArray(img.width * img.height * 4);
1197
+ rgba = new Uint8ClampedArray(img.width * img.height * 4);
816
1198
  for (let j = 0, k = 0; j < img.data.length; j++, k += 4) {
817
1199
  rgba[k] = img.data[j];
818
1200
  rgba[k + 1] = img.data[j];
819
1201
  rgba[k + 2] = img.data[j];
820
1202
  rgba[k + 3] = 255;
821
1203
  }
822
- imageData = new ImageData(rgba, img.width, img.height);
823
1204
  } else {
824
1205
  return null;
825
1206
  }
826
- ctx.putImageData(imageData, 0, 0);
827
- const dataUrl = canvas.toDataURL("image/png");
828
- const base64 = dataUrl.split(",")[1];
829
- const binaryStr = atob(base64);
830
- const bytes = new Uint8Array(binaryStr.length);
831
- for (let i = 0; i < binaryStr.length; i++) {
832
- bytes[i] = binaryStr.charCodeAt(i);
833
- }
834
- return bytes.buffer;
1207
+ const owned = new Uint8Array(rgba.byteLength);
1208
+ owned.set(rgba);
1209
+ return UPNG.encode([owned.buffer], img.width, img.height, 0);
835
1210
  } catch {
836
1211
  return null;
837
1212
  }
@@ -891,7 +1266,8 @@ async function applyWorkerConfig(pdfjsLib) {
891
1266
  pdfjsLib.GlobalWorkerOptions.workerSrc = _workerSrc;
892
1267
  }
893
1268
  }
894
- async function loadPdfDocument(data) {
1269
+ async function loadPdfDocument(data, signal) {
1270
+ signal?.throwIfAborted();
895
1271
  let pdfjsLib;
896
1272
  try {
897
1273
  pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
@@ -904,16 +1280,27 @@ async function loadPdfDocument(data) {
904
1280
  isEvalSupported: false,
905
1281
  useSystemFonts: true
906
1282
  });
907
- return { pdfjsLib, pdf: await loadingTask.promise };
1283
+ const pdf = await loadingTask.promise;
1284
+ signal?.throwIfAborted();
1285
+ return { pdfjsLib, pdf };
908
1286
  }
909
- async function extractTextLines(pdf) {
1287
+ async function extractTextLines(pdf, options) {
910
1288
  const allLines = [];
1289
+ const maxPages = safetyLimit("maxPages", options.maxPages ?? 5e3);
1290
+ const maxTextItems = safetyLimit("maxTextItems", options.maxTextItems ?? 1e6);
1291
+ let textItemCount = 0;
1292
+ if (pdf.numPages > maxPages)
1293
+ throw new RangeError(`PDF exceeds the ${maxPages}-page safety limit`);
911
1294
  for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
1295
+ options.signal?.throwIfAborted();
912
1296
  const page = await pdf.getPage(pageNum);
913
1297
  const content = await page.getTextContent();
914
1298
  const styleMap = content.styles || {};
915
1299
  const items = [];
916
1300
  for (const item of content.items) {
1301
+ if (++textItemCount > maxTextItems) {
1302
+ throw new RangeError(`PDF exceeds the ${maxTextItems}-text-item safety limit`);
1303
+ }
917
1304
  if (!item.str || item.str.trim().length === 0) continue;
918
1305
  const transform = item.transform || [1, 0, 0, 1, 0, 0];
919
1306
  const x = transform[4];
@@ -965,6 +1352,12 @@ async function extractTextLines(pdf) {
965
1352
  }
966
1353
  return allLines;
967
1354
  }
1355
+ function safetyLimit(name, value) {
1356
+ if (!Number.isSafeInteger(value) || value < 0) {
1357
+ throw new RangeError(`${name} must be a non-negative safe integer`);
1358
+ }
1359
+ return value;
1360
+ }
968
1361
  function detectBodyFontSize(lines) {
969
1362
  const sizes = lines.map((l) => Math.round(l.fontSize * 2) / 2);
970
1363
  return mode(sizes) || DEFAULT_FONT_SIZE;
@@ -1329,4 +1722,3 @@ export {
1329
1722
  pdfToContainer,
1330
1723
  configurePdfWorker
1331
1724
  };
1332
- //# sourceMappingURL=chunk-GVS2XXV6.js.map