@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.5

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 (246) hide show
  1. package/dist/api/color.d.ts +1 -24
  2. package/dist/api/color.js +2 -278
  3. package/dist/api/content.d.ts +10 -5
  4. package/dist/api/content.js +29 -13
  5. package/dist/api/descriptor.js +30 -3
  6. package/dist/api/flow.d.ts +55 -0
  7. package/dist/api/flow.js +71 -0
  8. package/dist/api/font-url.d.ts +1 -2
  9. package/dist/api/font-url.js +8 -74
  10. package/dist/api/forms.d.ts +3 -0
  11. package/dist/api/forms.js +3 -0
  12. package/dist/api/gradient.d.ts +1 -53
  13. package/dist/api/gradient.js +2 -97
  14. package/dist/api/index.d.ts +4 -0
  15. package/dist/api/index.js +3 -0
  16. package/dist/api/layout.d.ts +6 -0
  17. package/dist/api/layout.js +9 -3
  18. package/dist/api/page-builder.d.ts +4 -0
  19. package/dist/api/page-builder.js +4 -0
  20. package/dist/api/structure.d.ts +10 -2
  21. package/dist/api/structure.js +9 -6
  22. package/dist/api/table.d.ts +5 -3
  23. package/dist/api/table.js +101 -22
  24. package/dist/api/text.d.ts +8 -8
  25. package/dist/api/text.js +6 -0
  26. package/dist/canvas/painter.d.ts +2 -2
  27. package/dist/canvas/painter.js +2 -2
  28. package/dist/common/color-input.d.ts +24 -0
  29. package/dist/common/color-input.js +278 -0
  30. package/dist/common/diagnostics.d.ts +61 -0
  31. package/dist/common/diagnostics.js +77 -0
  32. package/dist/common/gradient.d.ts +53 -0
  33. package/dist/common/gradient.js +97 -0
  34. package/dist/edit/acroform-reader.js +7 -7
  35. package/dist/edit/appearance.js +3 -3
  36. package/dist/edit/attachments.d.ts +52 -0
  37. package/dist/edit/attachments.js +178 -0
  38. package/dist/edit/document.d.ts +38 -8
  39. package/dist/edit/document.js +208 -51
  40. package/dist/edit/fill.js +8 -23
  41. package/dist/edit/filters.d.ts +15 -0
  42. package/dist/edit/filters.js +157 -0
  43. package/dist/edit/index.d.ts +4 -1
  44. package/dist/edit/index.js +3 -1
  45. package/dist/edit/objects.d.ts +3 -4
  46. package/dist/edit/objects.js +5 -18
  47. package/dist/elements/canvas-element.d.ts +3 -0
  48. package/dist/elements/canvas-element.js +14 -3
  49. package/dist/elements/container-element.d.ts +8 -1
  50. package/dist/elements/container-element.js +49 -17
  51. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  52. package/dist/elements/forms/checkbox-element.js +12 -3
  53. package/dist/elements/forms/choice-element.d.ts +6 -1
  54. package/dist/elements/forms/choice-element.js +17 -6
  55. package/dist/elements/forms/field-font.d.ts +8 -0
  56. package/dist/elements/forms/field-font.js +5 -0
  57. package/dist/elements/forms/push-button-element.d.ts +6 -1
  58. package/dist/elements/forms/push-button-element.js +16 -4
  59. package/dist/elements/forms/radio-element.d.ts +2 -0
  60. package/dist/elements/forms/radio-element.js +12 -3
  61. package/dist/elements/forms/signature-element.d.ts +6 -1
  62. package/dist/elements/forms/signature-element.js +16 -4
  63. package/dist/elements/forms/text-field-element.d.ts +6 -1
  64. package/dist/elements/forms/text-field-element.js +16 -4
  65. package/dist/elements/image-element.d.ts +70 -21
  66. package/dist/elements/image-element.js +173 -52
  67. package/dist/elements/index.d.ts +3 -0
  68. package/dist/elements/index.js +3 -0
  69. package/dist/elements/layout/anchor-element.d.ts +8 -10
  70. package/dist/elements/layout/anchor-element.js +10 -18
  71. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  72. package/dist/elements/layout/bookmark-element.js +11 -20
  73. package/dist/elements/layout/column-break-element.d.ts +17 -0
  74. package/dist/elements/layout/column-break-element.js +26 -0
  75. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  76. package/dist/elements/layout/default-text-style-element.js +14 -31
  77. package/dist/elements/layout/deferred-element.d.ts +20 -4
  78. package/dist/elements/layout/deferred-element.js +41 -9
  79. package/dist/elements/layout/expanded-element.d.ts +11 -0
  80. package/dist/elements/layout/expanded-element.js +53 -18
  81. package/dist/elements/layout/flow-element.d.ts +109 -0
  82. package/dist/elements/layout/flow-element.js +317 -0
  83. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  84. package/dist/elements/layout/keep-together-element.js +33 -45
  85. package/dist/elements/layout/link-element.d.ts +8 -13
  86. package/dist/elements/layout/link-element.js +9 -19
  87. package/dist/elements/layout/padding-element.d.ts +18 -1
  88. package/dist/elements/layout/padding-element.js +63 -28
  89. package/dist/elements/layout/page-break-element.d.ts +4 -1
  90. package/dist/elements/layout/page-break-element.js +6 -1
  91. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  92. package/dist/elements/layout/page-builder-element.js +13 -2
  93. package/dist/elements/layout/positioned-element.d.ts +23 -4
  94. package/dist/elements/layout/positioned-element.js +42 -12
  95. package/dist/elements/layout/proxy-element.d.ts +47 -0
  96. package/dist/elements/layout/proxy-element.js +106 -0
  97. package/dist/elements/layout/region-element.d.ts +53 -0
  98. package/dist/elements/layout/region-element.js +108 -0
  99. package/dist/elements/layout/repeating-header-element.d.ts +9 -0
  100. package/dist/elements/layout/repeating-header-element.js +55 -13
  101. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  102. package/dist/elements/layout/rotated-box-element.js +27 -14
  103. package/dist/elements/layout/rotated-element.d.ts +9 -0
  104. package/dist/elements/layout/rotated-element.js +33 -2
  105. package/dist/elements/layout/struct-group.d.ts +9 -17
  106. package/dist/elements/layout/struct-group.js +10 -35
  107. package/dist/elements/line-element.d.ts +1 -0
  108. package/dist/elements/line-element.js +6 -0
  109. package/dist/elements/page-element.d.ts +10 -3
  110. package/dist/elements/page-element.js +39 -31
  111. package/dist/elements/pdf-document-element.d.ts +3 -0
  112. package/dist/elements/pdf-document-element.js +7 -0
  113. package/dist/elements/pdf-element.d.ts +105 -16
  114. package/dist/elements/pdf-element.js +118 -19
  115. package/dist/elements/rectangle-element.d.ts +22 -2
  116. package/dist/elements/rectangle-element.js +119 -55
  117. package/dist/elements/row-element.d.ts +17 -12
  118. package/dist/elements/row-element.js +89 -9
  119. package/dist/elements/svg-element.d.ts +30 -6
  120. package/dist/elements/svg-element.js +91 -26
  121. package/dist/elements/text-element.d.ts +48 -13
  122. package/dist/elements/text-element.js +161 -116
  123. package/dist/forms/acroform.d.ts +9 -1
  124. package/dist/forms/acroform.js +83 -34
  125. package/dist/forms/appearance.d.ts +17 -6
  126. package/dist/forms/appearance.js +18 -16
  127. package/dist/ir/display-list.d.ts +18 -3
  128. package/dist/layout/box-constraints.d.ts +17 -0
  129. package/dist/layout/box-constraints.js +31 -0
  130. package/dist/layout/collect-images.d.ts +6 -4
  131. package/dist/layout/collect-images.js +14 -8
  132. package/dist/layout/flex-item.d.ts +11 -0
  133. package/dist/layout/flex-item.js +51 -0
  134. package/dist/layout/fragmentation.d.ts +23 -9
  135. package/dist/layout/fragmentation.js +62 -19
  136. package/dist/layout/layout-cache.d.ts +53 -0
  137. package/dist/layout/layout-cache.js +110 -0
  138. package/dist/platform/browser-fs.d.ts +1 -2
  139. package/dist/platform/browser-fs.js +3 -6
  140. package/dist/platform/node-fs.d.ts +0 -1
  141. package/dist/platform/node-fs.js +0 -4
  142. package/dist/renderer/anchor-renderer.d.ts +2 -1
  143. package/dist/renderer/anchor-renderer.js +5 -4
  144. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  145. package/dist/renderer/bookmark-renderer.js +5 -4
  146. package/dist/renderer/canvas-renderer.d.ts +2 -1
  147. package/dist/renderer/canvas-renderer.js +5 -2
  148. package/dist/renderer/column-break-renderer.d.ts +8 -0
  149. package/dist/renderer/column-break-renderer.js +15 -0
  150. package/dist/renderer/container-renderer.d.ts +2 -1
  151. package/dist/renderer/container-renderer.js +6 -5
  152. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  153. package/dist/renderer/default-text-style-renderer.js +2 -3
  154. package/dist/renderer/deferred-renderer.d.ts +2 -1
  155. package/dist/renderer/deferred-renderer.js +2 -3
  156. package/dist/renderer/emoji-image.js +3 -1
  157. package/dist/renderer/expanded-renderer.d.ts +2 -1
  158. package/dist/renderer/expanded-renderer.js +2 -3
  159. package/dist/renderer/flow-renderer.d.ts +16 -0
  160. package/dist/renderer/flow-renderer.js +21 -0
  161. package/dist/renderer/form-field-renderer.d.ts +2 -1
  162. package/dist/renderer/form-field-renderer.js +16 -3
  163. package/dist/renderer/image-renderer.d.ts +2 -1
  164. package/dist/renderer/image-renderer.js +33 -53
  165. package/dist/renderer/index.d.ts +2 -0
  166. package/dist/renderer/index.js +2 -0
  167. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  168. package/dist/renderer/keep-together-renderer.js +2 -3
  169. package/dist/renderer/line-renderer.d.ts +2 -1
  170. package/dist/renderer/line-renderer.js +7 -2
  171. package/dist/renderer/link-renderer.d.ts +2 -1
  172. package/dist/renderer/link-renderer.js +36 -5
  173. package/dist/renderer/padding-renderer.d.ts +2 -1
  174. package/dist/renderer/padding-renderer.js +5 -5
  175. package/dist/renderer/page-break-renderer.d.ts +6 -5
  176. package/dist/renderer/page-break-renderer.js +14 -7
  177. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  178. package/dist/renderer/page-builder-renderer.js +2 -3
  179. package/dist/renderer/page-renderer.js +30 -10
  180. package/dist/renderer/pdf-backend.d.ts +4 -3
  181. package/dist/renderer/pdf-backend.js +14 -7
  182. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  183. package/dist/renderer/pdf-document-renderer.js +17 -17
  184. package/dist/renderer/pdf-renderer.js +20 -11
  185. package/dist/renderer/positioned-renderer.d.ts +18 -1
  186. package/dist/renderer/positioned-renderer.js +43 -6
  187. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  188. package/dist/renderer/rectangle-renderer.js +18 -8
  189. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  190. package/dist/renderer/repeating-header-renderer.js +6 -5
  191. package/dist/renderer/rotated-renderer.d.ts +2 -1
  192. package/dist/renderer/rotated-renderer.js +9 -5
  193. package/dist/renderer/row-renderer.d.ts +2 -1
  194. package/dist/renderer/row-renderer.js +6 -5
  195. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  196. package/dist/renderer/struct-group-renderer.js +2 -3
  197. package/dist/renderer/svg-renderer.d.ts +2 -1
  198. package/dist/renderer/svg-renderer.js +8 -2
  199. package/dist/renderer/text-renderer.d.ts +4 -6
  200. package/dist/renderer/text-renderer.js +114 -127
  201. package/dist/svg/gradients.js +5 -0
  202. package/dist/svg/index.d.ts +1 -1
  203. package/dist/svg/index.js +36 -6
  204. package/dist/svg/style.d.ts +2 -0
  205. package/dist/svg/style.js +12 -1
  206. package/dist/text/advance.d.ts +4 -0
  207. package/dist/text/advance.js +17 -4
  208. package/dist/text/bidi.d.ts +0 -2
  209. package/dist/text/bidi.js +0 -10
  210. package/dist/text/glyph-coverage.d.ts +1 -1
  211. package/dist/text/glyph-coverage.js +7 -3
  212. package/dist/text/hyphen-breaks.d.ts +17 -0
  213. package/dist/text/hyphen-breaks.js +54 -0
  214. package/dist/text/line-breaker.d.ts +55 -29
  215. package/dist/text/line-breaker.js +453 -188
  216. package/dist/text/line-metrics.d.ts +8 -3
  217. package/dist/text/line-metrics.js +11 -2
  218. package/dist/text/pdf-text-string.d.ts +21 -0
  219. package/dist/text/pdf-text-string.js +114 -0
  220. package/dist/text/text-decoration.d.ts +5 -5
  221. package/dist/text/text-decoration.js +5 -5
  222. package/dist/text/word-splitting.d.ts +15 -2
  223. package/dist/text/word-splitting.js +45 -13
  224. package/dist/utils/afm-parser.d.ts +2 -0
  225. package/dist/utils/afm-parser.js +10 -1
  226. package/dist/utils/dest-registry.d.ts +1 -1
  227. package/dist/utils/dest-registry.js +3 -2
  228. package/dist/utils/fetch-bytes.d.ts +19 -0
  229. package/dist/utils/fetch-bytes.js +79 -0
  230. package/dist/utils/flex-layout.d.ts +16 -14
  231. package/dist/utils/flex-layout.js +427 -217
  232. package/dist/utils/font-metrics.d.ts +4 -3
  233. package/dist/utils/image-helper.d.ts +59 -11
  234. package/dist/utils/image-helper.js +209 -81
  235. package/dist/utils/pdf-object-manager.d.ts +17 -6
  236. package/dist/utils/pdf-object-manager.js +70 -20
  237. package/dist/utils/renderer-registry.d.ts +49 -2
  238. package/dist/utils/renderer-registry.js +40 -0
  239. package/dist/utils/struct-tree.d.ts +24 -0
  240. package/dist/utils/struct-tree.js +61 -9
  241. package/dist/utils/ttf-parser.d.ts +4 -0
  242. package/dist/utils/ttf-parser.js +30 -6
  243. package/dist/validators/element-validator.js +2 -1
  244. package/package.json +1 -1
  245. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  246. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -26,6 +26,7 @@ export function featureKey(f) {
26
26
  }
27
27
  const features = (ligatures) => ligatures ? LATIN_FEATURES : NO_FEATURES;
28
28
  import { getArrayBuffer, isWindows1252 } from "./utf8-to-windows1252-encoder.js";
29
+ import { encodePdfText, isPlainPdfText } from "../text/pdf-text-string.js";
29
30
  import { isEmojiCodePoint } from "../text/emoji-codepoints.js";
30
31
  import { StructTree } from "./struct-tree.js";
31
32
  import { OutlineBuilder } from "./outline.js";
@@ -34,6 +35,14 @@ import { AcroFormCollector } from "../forms/acroform.js";
34
35
  // Enums come from the leaf config module (never in a cycle); the config type is
35
36
  // erased at runtime so it can come from the cyclic module safely.
36
37
  import { ColorMode, Orientation } from "../renderer/pdf-config.js";
38
+ import { Diagnostics } from "../common/diagnostics.js";
39
+ import { JpegFormatError } from "./image-helper.js";
40
+ /** The PDF color space of each image color space, and its channel count (for `/Decode`). */
41
+ const IMAGE_COLOR_SPACE = {
42
+ gray: { name: "DeviceGray", channels: 1 },
43
+ rgb: { name: "DeviceRGB", channels: 3 },
44
+ cmyk: { name: "DeviceCMYK", channels: 4 },
45
+ };
37
46
  export var FontStyle;
38
47
  (function (FontStyle) {
39
48
  FontStyle["Normal"] = "normal";
@@ -184,8 +193,8 @@ export class PDFObjectManager {
184
193
  /** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
185
194
  * key would carry the whole run's text, so every lookup would copy it into a fresh string. */
186
195
  this.shapeCache = new Map();
187
- /** Bumped by every font registration; `runAdvance` watches it to drop memoised widths, since a
188
- * family name can mean a different face afterwards. */
196
+ /** Bumped when a face is really ADDED (a repeat registration returns early, untouched); every
197
+ * memoised measurement watches it, since a family name can mean a different face afterwards. */
189
198
  this.fontEpoch = 0;
190
199
  // Per font, glyph -> the code points it stands for. A shaped glyph is often absent from the cmap (a
191
200
  // ligature) or maps back to a presentation form, so `ToUnicode` cannot come from the cmap alone.
@@ -220,10 +229,11 @@ export class PDFObjectManager {
220
229
  * character went missing instead of a server log nobody reads.
221
230
  */
222
231
  this.missingGlyphs = new Set();
232
+ /** The render's diagnostics channel (`onDiagnostic`); see `common/diagnostics.ts`. */
233
+ this.diagnostics = new Diagnostics();
223
234
  // Whether to kern. Document-level: measuring (via `runAdvance`, which reads this off the metrics)
224
- // and drawing (the backend emits a `TJ`) both consult it, so they can never disagree. OFF while the
225
- // embedded-font path (kern/GPOS) is unbuilt - a standard-14-only kerning would set the 14 system
226
- // fonts finer than a user's own font, which is worse than none. Flip to on when GPOS lands.
235
+ // and drawing (the backend emits a `TJ`) both consult it, so they can never disagree. The default
236
+ // here is off; `renderPdf` turns it on unless the document says `kerning: false`.
227
237
  this.kerningEnabled = false;
228
238
  if (pageSize)
229
239
  this.pageFormat = pageFormats[pageSize];
@@ -247,9 +257,18 @@ export class PDFObjectManager {
247
257
  setCompress(on) {
248
258
  this.compress = on;
249
259
  }
250
- reportMissingGlyph(codePoints) {
260
+ reportMissingGlyph(codePoints, about, page) {
251
261
  for (const cp of codePoints)
252
262
  this.missingGlyphs.add(cp);
263
+ const chars = codePoints.map((cp) => String.fromCodePoint(cp)).join("");
264
+ this.diagnostics.report({
265
+ code: "missing-glyphs",
266
+ severity: "warning",
267
+ message: `No font can draw ${JSON.stringify(chars)}; removed (PDF/A forbids .notdef).`,
268
+ about,
269
+ page,
270
+ fallback: "silent",
271
+ });
253
272
  }
254
273
  /** The dropped code points, in the order first seen. Empty for a document that hit none. */
255
274
  getMissingGlyphs() {
@@ -263,7 +282,7 @@ export class PDFObjectManager {
263
282
  }
264
283
  // A unique, binary-safe placeholder for a stream's bytes, swapped for ciphertext in finalizeEncryption().
265
284
  encToken(id) {
266
- return `JENC:${id}`;
285
+ return `\x00\x01JENC:${id}\x01\x00`;
267
286
  }
268
287
  // The same trick for a literal STRING. A separate marker because the two are swapped differently: a
269
288
  // stream keeps its delimiters and only the payload changes, a string is replaced whole by a hex one.
@@ -282,15 +301,28 @@ export class PDFObjectManager {
282
301
  * A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
283
302
  */
284
303
  pdfString(text) {
304
+ // A TEXT string: PDFDocEncoding where it can say everything, else UTF-16BE (text/pdf-text-string.ts).
305
+ const bytes = encodePdfText(text);
285
306
  if (!this.security) {
286
- return `(${text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")})`;
307
+ // Plain Latin-1 comes out of the Windows-1252 page encoder as the same bytes PDFDocEncoding reads
308
+ // back, so it stays a readable literal; anything else is written as hex, past that encoder.
309
+ if (isPlainPdfText(text)) {
310
+ return `(${text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")})`;
311
+ }
312
+ return `<${[...bytes].map((b) => b.toString(16).padStart(2, "0")).join("")}>`;
287
313
  }
288
314
  // Encrypt the bytes the unencrypted path would have written, so a viewer decrypts to the same value.
289
315
  // No escaping is applied: the result is emitted as a HEX string, which has no escapes to get wrong.
290
- const bytes = new Uint8Array(getArrayBuffer(text));
291
316
  const id = this.strJobs.push(bytes) - 1;
292
317
  return this.strToken(id);
293
318
  }
319
+ /**
320
+ * A URI: 7-bit ASCII by definition, so anything else is percent-encoded as UTF-8 - the way a browser
321
+ * writes the link - rather than squeezed into a text encoding.
322
+ */
323
+ pdfUri(uri) {
324
+ return this.pdfString(uri.replace(/[^\x20-\x7e]/gu, (c) => encodeURIComponent(c)));
325
+ }
294
326
  // The single encryption choke-point every stream emitter routes through (stream(), registerImage). Without
295
327
  // a security handler: the bytes as a latin1 string + their length. With one: register the raw bytes for the
296
328
  // finalize pass and return a placeholder + the pre-computable encrypted length (16-byte IV + PKCS#7 padding).
@@ -371,7 +403,7 @@ export class PDFObjectManager {
371
403
  return this.parentObjectNumber;
372
404
  }
373
405
  // Registers an image
374
- registerImage(width, height, imageType, imageData, smaskData) {
406
+ registerImage(width, height, imageType, imageData, smaskData, color = { space: "rgb" }) {
375
407
  // A transparent PNG carries its alpha as a separate DeviceGray /SMask image the viewer composites with.
376
408
  let smaskEntry = "";
377
409
  if (smaskData) {
@@ -389,14 +421,23 @@ ${s.body}
389
421
  endstream`;
390
422
  smaskEntry = ` /SMask ${this.addObject(smaskObject)} 0 R\n`;
391
423
  }
424
+ // PDF/A: the sRGB output intent forbids DeviceCMYK.
425
+ if (color.space === "cmyk" && this.outputIntent !== undefined) {
426
+ throw new JpegFormatError("a CMYK JPEG in a PDF/A document: its sRGB output intent forbids DeviceCMYK. " +
427
+ "Convert the image to RGB first.");
428
+ }
429
+ const space = IMAGE_COLOR_SPACE[color.space];
430
+ const decode = color.inverted
431
+ ? ` /Decode [${Array.from({ length: space.channels }, () => "1 0").join(" ")}]\n`
432
+ : "";
392
433
  const img = this.streamPayload(bytesFromLatin1(imageData));
393
434
  const imageObject = `<< /Type /XObject
394
435
  /Subtype /Image
395
436
  /Width ${width}
396
437
  /Height ${height}
397
- /ColorSpace /DeviceRGB
438
+ /ColorSpace /${space.name}
398
439
  /BitsPerComponent 8
399
- /Filter /${imageType}
440
+ ${decode} /Filter /${imageType}
400
441
  ${smaskEntry} /Length ${img.length} >>
401
442
  stream
402
443
  ${img.body}
@@ -473,7 +514,7 @@ endstream`;
473
514
  async finalizeEncryption() {
474
515
  if (!this.security)
475
516
  return;
476
- if (this.outputIntent !== undefined || this.attachments.length > 0) {
517
+ if (this.outputIntent !== undefined) {
477
518
  throw new Error("@jasy/pdf: cannot encrypt a PDF/A document - PDF/A forbids encryption.");
478
519
  }
479
520
  for (let id = 0; id < this.encJobs.length; id++) {
@@ -565,10 +606,10 @@ endstream`;
565
606
  }
566
607
  // Registers a font
567
608
  registerFont(fontName, fontStyle = FontStyle.Normal, fullName = fontName) {
568
- this.fontEpoch++;
569
609
  if (this.fonts.hasFont(fontName, fontStyle)) {
570
610
  return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
571
611
  }
612
+ this.fontEpoch++;
572
613
  this.verticalsCache.delete(fontName);
573
614
  this.decorationCache.delete(fontName);
574
615
  const data = STANDARD_AFM[fullName];
@@ -596,7 +637,6 @@ endstream`;
596
637
  // metrics and emits its PDF font objects. All variants share the family `name`; bold/italic are
597
638
  // separate .ttf files registered under the same name with a different style.
598
639
  registerCustomFont(name, data, style = FontStyle.Normal) {
599
- this.fontEpoch++;
600
640
  let byStyle = this.customFonts.get(name);
601
641
  if (!byStyle) {
602
642
  byStyle = new Map();
@@ -616,6 +656,7 @@ endstream`;
616
656
  "is loaded asynchronously, so it happens one step earlier in renderPdf.");
617
657
  }
618
658
  byStyle.set(style, new TTFParser(isWoff(data) ? woffToSfnt(data) : data));
659
+ this.fontEpoch++; // only once the face is really there: a font that fails to parse adds nothing
619
660
  this.verticalsCache.delete(name); // this family now answers from the .ttf, not from an AFM
620
661
  this.decorationCache.delete(name);
621
662
  // Emission (the PDF font objects + page resource) is DEFERRED to first use via ensureEmitted(),
@@ -742,7 +783,7 @@ endstream`;
742
783
  return isWindows1252(codePoint);
743
784
  }
744
785
  /**
745
- * Whether the document draws this code point through the colour-emoji path instead of the text
786
+ * Whether the document draws this code point through the color-emoji path instead of the text
746
787
  * font - a fallback emoji FONT, or an image source (`Document({ emoji })`). Such a code point is
747
788
  * missing from the text font ON PURPOSE, so glyph coverage must leave it alone. Mirrors exactly
748
789
  * what `getCharWidth` measures it as, or a dropped emoji would have been measured and not drawn.
@@ -873,7 +914,9 @@ endstream`;
873
914
  const all = ttf.glyphWidths();
874
915
  const w = [...used]
875
916
  .sort((a, b) => a - b)
876
- .map((g) => `${g} [${all[g] ?? 1000}]`)
917
+ // Four decimals: a 2048-unit font's widths are not whole thousandths, and rounding them drew every
918
+ // line a fraction wider or narrower than it was measured. A 1000-unit font stays byte-identical.
919
+ .map((g) => `${g} [${Number((all[g] ?? 1000).toFixed(4))}]`)
877
920
  .join(" ");
878
921
  return (`<< /Type /Font /Subtype /CIDFontType2 /BaseFont /${pdfName(name)} ` +
879
922
  `/CIDSystemInfo << /Registry ${this.pdfString("Adobe")} /Ordering ${this.pdfString("Identity")} /Supplement 0 >> ` +
@@ -972,14 +1015,20 @@ endstream`;
972
1015
  * `GPOS` tables are unparsed), so it returns zeros - never a wrong value, just no adjustment.
973
1016
  */
974
1017
  /** Kerning between adjacent GLYPHS - what a shaped run needs, and what the backend already holds.
975
- * Same lookup as the character path; `getKerning` is keyed by glyph id either way. */
1018
+ * Same lookup as the character path; `getKerning` is keyed by glyph id either way. A pair that
1019
+ * touches a space is never kerned, as on the character path: the line breaker measures words and
1020
+ * spaces apart, so a pair across a space would be drawn but never measured. */
976
1021
  getGlyphKernPairs(glyphs, fontFamily, fontStyle) {
977
1022
  const ttf = this.getCustomFont(fontFamily, fontStyle);
978
1023
  if (!ttf)
979
1024
  return [];
1025
+ const space = ttf.getGlyphIndex(0x20);
1026
+ const noBreakSpace = ttf.getGlyphIndex(0xa0);
1027
+ const isSpace = (g) => g !== 0 && (g === space || g === noBreakSpace); // 0 = no such glyph
980
1028
  const pairs = [];
981
1029
  for (let i = 0; i < glyphs.length - 1; i++) {
982
- pairs.push(ttf.getKerning(glyphs[i], glyphs[i + 1]));
1030
+ const [a, b] = [glyphs[i], glyphs[i + 1]];
1031
+ pairs.push(isSpace(a) || isSpace(b) ? 0 : ttf.getKerning(a, b));
983
1032
  }
984
1033
  return pairs;
985
1034
  }
@@ -1004,7 +1053,8 @@ endstream`;
1004
1053
  for (let i = 0; i < chars.length - 1; i++) {
1005
1054
  const a = chars[i];
1006
1055
  const b = chars[i + 1];
1007
- if (a === " " || b === " ") {
1056
+ // A no-break space is a space: never kerned (the AFM names it `space`, with pairs of its own).
1057
+ if (a === " " || b === " " || a === "\u00a0" || b === "\u00a0") {
1008
1058
  out.push(0);
1009
1059
  }
1010
1060
  else if (ttf) {
@@ -1,3 +1,33 @@
1
+ import type { Offset } from "../layout/box-constraints.js";
2
+ import type { IRNode } from "../ir/display-list.js";
3
+ import type { PDFObjectManager } from "./pdf-object-manager.js";
4
+ /**
5
+ * Where an element is drawn. `origin` is the ABSOLUTE top-left that the element's own offset is
6
+ * relative to; a renderer adds it to its position and hands its children the origin THEY are relative
7
+ * to. `frame` is the absolute top-left of the nearest positioning frame (the page's content box, or a
8
+ * `relative` Box): a `Positioned` is laid out relative to it and draws from there, at its own place in
9
+ * the tree - tree order is paint order.
10
+ */
11
+ export interface RenderScope {
12
+ origin: Offset;
13
+ frame: Offset;
14
+ /** Where a `Positioned` with a `zIndex` hands its drawing, for the nearest stacking context to sort.
15
+ * It still RENDERS at its own place, so everything it registers there (its tag's parent) is right. */
16
+ stacked?: {
17
+ zIndex: number;
18
+ nodes: IRNode[];
19
+ }[];
20
+ }
21
+ /** The scope for the children of an element at `offset` (relative to `scope.origin`). */
22
+ export declare function within(scope: RenderScope, offset: {
23
+ x?: number;
24
+ y?: number;
25
+ }): RenderScope;
26
+ /**
27
+ * Renders one element to IR. Typed so that a call which forgets to pass the scope on does not
28
+ * compile - a forgotten origin draws at the page corner without a word.
29
+ */
30
+ export type ElementRenderer = (element: any, objectManager: PDFObjectManager, scope: RenderScope) => Promise<IRNode[]>;
1
31
  /**
2
32
  * Thrown when an element reaches the render pass with no registered renderer.
3
33
  *
@@ -10,9 +40,26 @@ export declare class MissingRendererError extends Error {
10
40
  readonly elementName: string;
11
41
  constructor(elementName: string);
12
42
  }
43
+ /**
44
+ * Thrown when one element INSTANCE is drawn twice on the same page. An element holds one position - the
45
+ * place its last layout gave it - so both copies would land there and one would silently go missing.
46
+ */
47
+ export declare class ElementReusedError extends Error {
48
+ readonly elementName: string;
49
+ constructor(elementName: string);
50
+ }
13
51
  export declare class RendererRegistry {
14
52
  private static renderers;
15
- static register(elementClass: Function, renderer: Function): void;
16
- static getRenderer(element: object): Function;
53
+ private static drawnOnPage;
54
+ static register(elementClass: Function, renderer: ElementRenderer): void;
55
+ static getRenderer(element: object): ElementRenderer;
56
+ /** A new page starts for this document: nothing is drawn on it yet. */
57
+ static beginPage(objectManager: PDFObjectManager): void;
58
+ /**
59
+ * Draws `element` with its renderer. Every draw goes through here, so it is where reuse is caught - for
60
+ * an element that DRAWS something: one that draws nothing (a Spacer) has no place to land wrongly, and
61
+ * reusing it stays harmless.
62
+ */
63
+ static render(element: object, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
17
64
  static isRendererAsync(renderer: Function): boolean;
18
65
  }
@@ -1,3 +1,10 @@
1
+ /** The scope for the children of an element at `offset` (relative to `scope.origin`). */
2
+ export function within(scope, offset) {
3
+ return {
4
+ ...scope,
5
+ origin: { x: scope.origin.x + (offset.x ?? 0), y: scope.origin.y + (offset.y ?? 0) },
6
+ };
7
+ }
1
8
  /**
2
9
  * Thrown when an element reaches the render pass with no registered renderer.
3
10
  *
@@ -15,6 +22,18 @@ export class MissingRendererError extends Error {
15
22
  this.elementName = elementName;
16
23
  }
17
24
  }
25
+ /**
26
+ * Thrown when one element INSTANCE is drawn twice on the same page. An element holds one position - the
27
+ * place its last layout gave it - so both copies would land there and one would silently go missing.
28
+ */
29
+ export class ElementReusedError extends Error {
30
+ constructor(elementName) {
31
+ super(`@jasy/pdf: the same ${elementName} element is used twice on one page. An element has ONE ` +
32
+ `position, so both copies would be drawn in the same place. Create it twice instead - call the ` +
33
+ `factory again (e.g. a function \`const divider = () => Divider()\`) or place \`element.clone()\`.`);
34
+ this.elementName = elementName;
35
+ }
36
+ }
18
37
  export class RendererRegistry {
19
38
  static register(elementClass, renderer) {
20
39
  if (!RendererRegistry.renderers.has(elementClass)) {
@@ -29,8 +48,29 @@ export class RendererRegistry {
29
48
  throw new MissingRendererError(element.constructor.name);
30
49
  return renderer;
31
50
  }
51
+ /** A new page starts for this document: nothing is drawn on it yet. */
52
+ static beginPage(objectManager) {
53
+ RendererRegistry.drawnOnPage.set(objectManager, new WeakSet());
54
+ }
55
+ /**
56
+ * Draws `element` with its renderer. Every draw goes through here, so it is where reuse is caught - for
57
+ * an element that DRAWS something: one that draws nothing (a Spacer) has no place to land wrongly, and
58
+ * reusing it stays harmless.
59
+ */
60
+ static async render(element, objectManager, scope) {
61
+ const nodes = await RendererRegistry.getRenderer(element)(element, objectManager, scope);
62
+ const drawn = RendererRegistry.drawnOnPage.get(objectManager);
63
+ if (drawn && nodes.length > 0) {
64
+ if (drawn.has(element))
65
+ throw new ElementReusedError(element.constructor.name);
66
+ drawn.add(element);
67
+ }
68
+ return nodes;
69
+ }
32
70
  static isRendererAsync(renderer) {
33
71
  return renderer.constructor.name === "AsyncFunction";
34
72
  }
35
73
  }
36
74
  RendererRegistry.renderers = new Map();
75
+ // What each document has drawn on its current page (keyed per document: two can render at once).
76
+ RendererRegistry.drawnOnPage = new WeakMap();
@@ -24,18 +24,42 @@ export declare class StructTree {
24
24
  private byStructId;
25
25
  private stack;
26
26
  private pageObjByStructParents;
27
+ private annotParents;
28
+ private carried;
27
29
  /** Register a structure element for a logical element (keyed by its stable `structId`, so a paragraph or
28
30
  * table split across pages resolves to the SAME element - MCIDs then accrue from every page). Its parent
29
31
  * is the currently-open container, else the Document. Containers also push()/pop() around their children. */
30
32
  openElement(structId: number, role: string, opts?: {
31
33
  alt?: string;
32
34
  }): number;
35
+ /**
36
+ * An element no layout element stands behind - an inline link inside a paragraph: a child of `parent`,
37
+ * new every time (nothing to deduplicate).
38
+ */
39
+ addElement(role: string, parent: number, opts?: {
40
+ alt?: string;
41
+ }): number;
42
+ /** A piece of paragraph `owner` ended inside link element `key`: its next piece may continue it. */
43
+ carryLink(owner: number, target: string, key: number): void;
44
+ /** The link the previous piece of `owner` ended in. Taken by the next piece, whether it continues it. */
45
+ takeCarriedLink(owner: number): {
46
+ target: string;
47
+ key: number;
48
+ } | undefined;
33
49
  push(key: number): void;
34
50
  pop(): void;
35
51
  /** Begin a page: allocates its StructParents index (its slot in the ParentTree). */
36
52
  beginPage(): PageStructContext;
37
53
  /** Tie a page's StructParents index to its now-known page object number (for /Pg references). */
38
54
  setPageObject(structParents: number, pageObjNum: number): void;
55
+ /**
56
+ * PDF/UA 7.18: an annotation belongs to a structure element (a link to its `/Link`, a widget to its
57
+ * `/Form`). Returns the `/StructParent` the annotation dictionary carries - its own slot in the
58
+ * ParentTree, numbered alongside the pages'.
59
+ */
60
+ annotationParent(key: number): number;
61
+ /** The annotation object, once written: an `/OBJR` kid of its element, on the page that lists it. */
62
+ linkAnnotation(key: number, obj: number, pageStructParents: number): void;
39
63
  /** Link a page-local MCID to an element (called by the backend while serializing). */
40
64
  linkMarkedContent(key: number, structParents: number, mcid: number): void;
41
65
  /** Emit the struct-tree objects and return the catalog additions (`/MarkInfo`, `/StructTreeRoot`, `/Lang`).
@@ -32,6 +32,9 @@ export class StructTree {
32
32
  this.byStructId = new Map(); // element structId -> element key (dedup across pages)
33
33
  this.stack = []; // open containers; top is the current parent
34
34
  this.pageObjByStructParents = new Map();
35
+ this.annotParents = new Map(); // an annotation's /StructParent -> its element key
36
+ // An inline link a page break cut: paragraph structId -> the link it ended in, for its next piece.
37
+ this.carried = new Map();
35
38
  }
36
39
  /** Register a structure element for a logical element (keyed by its stable `structId`, so a paragraph or
37
40
  * table split across pages resolves to the SAME element - MCIDs then accrue from every page). Its parent
@@ -48,9 +51,29 @@ export class StructTree {
48
51
  alt: opts?.alt,
49
52
  parent: this.stack[this.stack.length - 1],
50
53
  mc: [],
54
+ annots: [],
51
55
  });
52
56
  return key;
53
57
  }
58
+ /**
59
+ * An element no layout element stands behind - an inline link inside a paragraph: a child of `parent`,
60
+ * new every time (nothing to deduplicate).
61
+ */
62
+ addElement(role, parent, opts) {
63
+ const key = this.counter++;
64
+ this.elems.set(key, { key, role, alt: opts?.alt, parent, mc: [], annots: [] });
65
+ return key;
66
+ }
67
+ /** A piece of paragraph `owner` ended inside link element `key`: its next piece may continue it. */
68
+ carryLink(owner, target, key) {
69
+ this.carried.set(owner, { target, key });
70
+ }
71
+ /** The link the previous piece of `owner` ended in. Taken by the next piece, whether it continues it. */
72
+ takeCarriedLink(owner) {
73
+ const carried = this.carried.get(owner);
74
+ this.carried.delete(owner);
75
+ return carried;
76
+ }
54
77
  push(key) {
55
78
  this.stack.push(key);
56
79
  }
@@ -65,6 +88,20 @@ export class StructTree {
65
88
  setPageObject(structParents, pageObjNum) {
66
89
  this.pageObjByStructParents.set(structParents, pageObjNum);
67
90
  }
91
+ /**
92
+ * PDF/UA 7.18: an annotation belongs to a structure element (a link to its `/Link`, a widget to its
93
+ * `/Form`). Returns the `/StructParent` the annotation dictionary carries - its own slot in the
94
+ * ParentTree, numbered alongside the pages'.
95
+ */
96
+ annotationParent(key) {
97
+ const structParent = this.structParentsCounter++;
98
+ this.annotParents.set(structParent, key);
99
+ return structParent;
100
+ }
101
+ /** The annotation object, once written: an `/OBJR` kid of its element, on the page that lists it. */
102
+ linkAnnotation(key, obj, pageStructParents) {
103
+ this.elems.get(key)?.annots.push({ obj, structParents: pageStructParents });
104
+ }
68
105
  /** Link a page-local MCID to an element (called by the backend while serializing). */
69
106
  linkMarkedContent(key, structParents, mcid) {
70
107
  this.elems.get(key)?.mc.push({ structParents, mcid });
@@ -83,11 +120,24 @@ export class StructTree {
83
120
  objNum.set(key, om.addObject(""));
84
121
  const pg = (structParents) => this.pageObjByStructParents.get(structParents);
85
122
  const childrenOf = (parent) => [...this.elems.values()].filter((e) => e.parent === parent); // Map order = reading order
123
+ const before = (a, b) => a.structParents - b.structParents || a.mcid - b.mcid;
124
+ const firstOf = (e) => [...e.mc, ...childrenOf(e.key).flatMap((c) => firstOf(c) ?? [])].sort(before)[0];
125
+ const mcr = (m) => `<< /Type /MCR /Pg ${pg(m.structParents)} 0 R /MCID ${m.mcid} >>`;
86
126
  // One StructElem per registered element: /K = its child elements ++ its marked-content references.
127
+ // An element holding BOTH (a paragraph with an inline link) interleaves them in reading order.
87
128
  for (const e of this.elems.values()) {
129
+ const children = childrenOf(e.key);
130
+ const content = children.length > 0 && e.mc.length > 0
131
+ ? [
132
+ ...children.map((c) => ({ at: firstOf(c), ref: `${objNum.get(c.key)} 0 R` })),
133
+ ...e.mc.map((m) => ({ at: m, ref: mcr(m) })),
134
+ ]
135
+ .sort((a, b) => (a.at && b.at ? before(a.at, b.at) : 0))
136
+ .map((k) => k.ref)
137
+ : [...children.map((c) => `${objNum.get(c.key)} 0 R`), ...e.mc.map(mcr)];
88
138
  const kids = [
89
- ...childrenOf(e.key).map((c) => `${objNum.get(c.key)} 0 R`),
90
- ...e.mc.map((m) => `<< /Type /MCR /Pg ${pg(m.structParents)} 0 R /MCID ${m.mcid} >>`),
139
+ ...content,
140
+ ...e.annots.map((a) => `<< /Type /OBJR /Pg ${pg(a.structParents)} 0 R /Obj ${a.obj} 0 R >>`),
91
141
  ].join(" ");
92
142
  const alt = e.alt ? ` /Alt ${om.pdfString(e.alt)}` : "";
93
143
  // PDF/UA 7.5: a header cell needs a Scope so a reader can associate data cells with it. Our headers
@@ -110,17 +160,19 @@ export class StructTree {
110
160
  byPage.set(m.structParents, list);
111
161
  }
112
162
  }
113
- const nums = [...byPage.keys()]
114
- .sort((a, b) => a - b)
115
- .map((sp) => {
116
- const refs = byPage
117
- .get(sp)
163
+ // A page's entry is an array indexed by MCID; an annotation's is its element itself.
164
+ const entries = new Map();
165
+ for (const [sp, list] of byPage) {
166
+ const refs = list
118
167
  .slice()
119
168
  .sort((a, b) => a.mcid - b.mcid)
120
169
  .map((r) => `${objNum.get(r.key)} 0 R`)
121
170
  .join(" ");
122
- return `${sp} [ ${refs} ]`;
123
- });
171
+ entries.set(sp, `[ ${refs} ]`);
172
+ }
173
+ for (const [sp, key] of this.annotParents)
174
+ entries.set(sp, `${objNum.get(key)} 0 R`);
175
+ const nums = [...entries.keys()].sort((a, b) => a - b).map((sp) => `${sp} ${entries.get(sp)}`);
124
176
  om.replaceObject(parentTreeNum, `<< /Nums [ ${nums.join(" ")} ] >>`);
125
177
  om.replaceObject(rootNum, `<< /Type /StructTreeRoot /K ${docNum} 0 R /ParentTree ${parentTreeNum} 0 R ` +
126
178
  `/ParentTreeNextKey ${this.structParentsCounter} >>`);
@@ -51,6 +51,10 @@ export interface ColorGlyphLayer {
51
51
  }
52
52
  /** Sorts x-intervals and unions the overlapping ones. */
53
53
  export declare function mergeSpans(spans: Array<[number, number]>): Array<[number, number]>;
54
+ /** Thrown when font bytes are a format jasy cannot embed - said by name, never embedded broken. */
55
+ export declare class FontFormatError extends Error {
56
+ constructor(message: string);
57
+ }
54
58
  export declare class TTFParser {
55
59
  private data;
56
60
  unitsPerEm: number;
@@ -46,6 +46,12 @@ function applyAffine(m, x, y) {
46
46
  function detScale(m) {
47
47
  return Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
48
48
  }
49
+ /** Thrown when font bytes are a format jasy cannot embed - said by name, never embedded broken. */
50
+ export class FontFormatError extends Error {
51
+ constructor(message) {
52
+ super(`@jasy/pdf: ${message}`);
53
+ }
54
+ }
49
55
  export class TTFParser {
50
56
  constructor(data) {
51
57
  this.data = data;
@@ -88,6 +94,17 @@ export class TTFParser {
88
94
  // subtables (coverage + classDef) per pair, and a long document repeats the same pairs thousands
89
95
  // of times, so the first lookup of a pair pays; the rest are O(1).
90
96
  this.kernCache = new Map();
97
+ // Every font path meets here (bytes, a file, a URL, an unwrapped WOFF/WOFF2). A CFF font has the
98
+ // same tables up front and measured fine, then went into the PDF as a TrueType program it is not.
99
+ const tag = String.fromCharCode(...data.subarray(0, 4));
100
+ if (tag === "OTTO") {
101
+ throw new FontFormatError("this is an OpenType font with PostScript (CFF) outlines, which jasy does not embed yet - " +
102
+ "use its TrueType-flavoured version (.ttf, or a .woff/.woff2 made from one)");
103
+ }
104
+ if (tag === "ttcf") {
105
+ throw new FontFormatError("this is a TrueType Collection (.ttc), which jasy does not read yet - extract the one face " +
106
+ "you need into a .ttf");
107
+ }
91
108
  this.readTableDirectory();
92
109
  const head = this.table("head").offset;
93
110
  this.unitsPerEm = u16(this.data, head + 18);
@@ -132,11 +149,12 @@ export class TTFParser {
132
149
  glyphCount() {
133
150
  return this.numGlyphs;
134
151
  }
135
- // Advance width of every glyph in PDF glyph space (the /W array of the CIDFont).
152
+ // Advance width of every glyph in PDF glyph space (the /W array of the CIDFont), NOT rounded: layout
153
+ // measures the exact width, so the drawn one must be the same number.
136
154
  glyphWidths() {
137
155
  const out = [];
138
156
  for (let g = 0; g < this.numGlyphs; g++) {
139
- out.push(this.toGlyphSpace(this.getAdvanceWidth(g)));
157
+ out.push((this.getAdvanceWidth(g) * 1000) / this.unitsPerEm);
140
158
  }
141
159
  return out;
142
160
  }
@@ -392,17 +410,23 @@ export class TTFParser {
392
410
  }
393
411
  if (kernLookups.size === 0)
394
412
  return;
395
- // The PairPos subtables of those lookups (a lookup of another type in the set is ignored).
413
+ // The PairPos subtables of those lookups (a lookup of another type in the set is ignored). A large
414
+ // font wraps its lookups in Extension subtables (type 9) for their 32-bit offsets - read through.
396
415
  const lookupCount = u16(this.data, lookupListOffset);
397
416
  for (const idx of kernLookups) {
398
417
  if (idx >= lookupCount)
399
418
  continue;
400
419
  const lookup = lookupListOffset + u16(this.data, lookupListOffset + 2 + idx * 2);
401
- if (u16(this.data, lookup) !== 2)
402
- continue; // lookupType 2 = pair adjustment
420
+ const type = u16(this.data, lookup);
421
+ if (type !== 2 && type !== 9)
422
+ continue; // 2 = pair adjustment, 9 = extension
403
423
  const subTableCount = u16(this.data, lookup + 4);
404
424
  for (let s = 0; s < subTableCount; s++) {
405
- this.gposPairPos.push(lookup + u16(this.data, lookup + 6 + s * 2));
425
+ const sub = lookup + u16(this.data, lookup + 6 + s * 2);
426
+ if (type === 2)
427
+ this.gposPairPos.push(sub);
428
+ else if (u16(this.data, sub + 2) === 2)
429
+ this.gposPairPos.push(sub + u32(this.data, sub + 4));
406
430
  }
407
431
  }
408
432
  if (this.gposPairPos.length > 0)
@@ -1,3 +1,4 @@
1
+ import { flexibleOf } from "../layout/flex-item.js";
1
2
  import { PDFDocumentElement } from "../elements/pdf-document-element.js";
2
3
  import { FlexiblePDFElement, hasChildProp, SizedPDFElement, } from "../elements/pdf-element.js";
3
4
  export class Validator {
@@ -58,7 +59,7 @@ export class Validator {
58
59
  }
59
60
  // Ensure a flexible element does not contain another flexible element
60
61
  if (hasChildProp(element)) {
61
- if (element.child instanceof FlexiblePDFElement) {
62
+ if (flexibleOf(element.child) !== undefined) {
62
63
  throw new Error(`Flexible element ${element.constructor.name} cannot hold another flexible element`);
63
64
  }
64
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jasy/pdf",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Declarative, component-based PDF generation in pure TypeScript - Flutter-style components, real AFM font metrics, a hand-rolled PDF writer. No headless browser, no Java.",
5
5
  "keywords": [
6
6
  "declarative",
@@ -1,14 +0,0 @@
1
- import { SizedElement, WithChildren, SizedPDFElement, LayoutContext } from "../pdf-element.js";
2
- import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
3
- interface ContainerElementParams extends SizedElement, WithChildren {
4
- color?: [number, number, number];
5
- backgroundColor?: [number, number, number];
6
- borderWidth?: number;
7
- }
8
- export declare class SizedContainerElement extends SizedPDFElement {
9
- private children;
10
- constructor({ width, height, children }: ContainerElementParams);
11
- calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
12
- getProps(): ContainerElementParams;
13
- }
14
- export {};