@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
@@ -31,9 +31,10 @@ export interface FontMetrics {
31
31
  /** Whether the document draws this code point as colour emoji rather than from the text font - it
32
32
  * is missing from that font on purpose, so glyph coverage must not touch it. */
33
33
  rendersAsEmoji?(codePoint: number, fontFamily: string, fontStyle: FontStyle): boolean;
34
- /** Told when a code point had to be removed because no font could draw it. Optional: a metrics
35
- * object used only for measuring (a test double) has nothing to report to. */
36
- reportMissingGlyph?(codePoints: number[]): void;
34
+ /** Told when a code point had to be removed because no font could draw it - `about` is the element
35
+ * it was removed from. Optional: a metrics object used only for measuring (a test double) has
36
+ * nothing to report to. */
37
+ reportMissingGlyph?(codePoints: number[], about?: object, page?: number): void;
37
38
  /** How many glyphs the run will DRAW, when a ligature made that differ from its code-point count.
38
39
  * Absent or `undefined` means "the same", which is every Latin run. */
39
40
  shapedGlyphCount?(text: string, fontFamily?: string, fontStyle?: FontStyle, ligatures?: boolean): number | undefined;
@@ -3,11 +3,56 @@ declare global {
3
3
  getUint24(byteOffset: number, littleEndian?: boolean): number;
4
4
  }
5
5
  }
6
- interface ImageDimensions {
6
+ /** Dots per inch, per axis. */
7
+ export interface Resolution {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ export interface ImageDimensions {
12
+ /** In pixels - what the XObject declares. */
13
+ width: number;
14
+ height: number;
15
+ /** The resolution the file declares; absent when it declares none. */
16
+ dpi?: Resolution;
17
+ /** How the stored pixels turn upright (EXIF, a JPEG only); absent when they already are. */
18
+ orientation?: Orientation;
19
+ }
20
+ /**
21
+ * EXIF orientation: 2 mirrored, 3 turned half, 4 flipped, 5 transposed, 6 turned a quarter clockwise,
22
+ * 7 transversed, 8 turned a quarter counterclockwise. 5-8 swap width and height.
23
+ */
24
+ export type Orientation = 2 | 3 | 4 | 5 | 6 | 7 | 8;
25
+ /**
26
+ * The matrix that turns the image's unit square upright, inside it - PDF's image space, y up, the first
27
+ * stored row at the top. Applied before the placement scales it, so the box stays the box.
28
+ */
29
+ export declare const ORIENTATION_MATRIX: Record<Orientation, readonly number[]>;
30
+ /** The resolution of a file that declares none: CSS's reference pixel, 1/96 inch. */
31
+ export declare const DEFAULT_DPI = 96;
32
+ /**
33
+ * An image's own size in points, upright: its pixels at the resolution the file declares, else 96 dpi,
34
+ * turned by its orientation.
35
+ */
36
+ export declare function naturalSize(dims: ImageDimensions): {
7
37
  width: number;
8
38
  height: number;
39
+ };
40
+ /** Thrown for a JPEG we cannot embed as it is. Says what it is and what to do. */
41
+ export declare class JpegFormatError extends Error {
42
+ constructor(message: string);
43
+ }
44
+ /** The colors an image's samples are in. */
45
+ export type ImageColorSpace = "gray" | "rgb" | "cmyk";
46
+ /** What an image's samples mean: their color space, and whether they are stored inverted. */
47
+ export interface ImageColor {
48
+ space: ImageColorSpace;
49
+ /** Photoshop stores CMYK inverted (an Adobe `APP14` segment). */
50
+ inverted?: boolean;
9
51
  }
10
- export declare function getImageDimensions(buffer: Uint8Array): Promise<ImageDimensions>;
52
+ /** The color of a JPEG as it is embedded (`DCTDecode`): the space it was saved in. */
53
+ export declare function jpegColor(bytes: Uint8Array): ImageColor;
54
+ /** Read from the header alone, synchronously - layout can ask it. */
55
+ export declare function readImageDimensions(buffer: Uint8Array): ImageDimensions;
11
56
  /**
12
57
  * Converts the given image to grayscale and returns its binary data.
13
58
  * @param imagePath Path to the input image file.
@@ -26,19 +71,22 @@ export declare function decodePngToRgbFlate(pngBuffer: Uint8Array): Promise<{
26
71
  height: number;
27
72
  }>;
28
73
  export declare function convertImageToGrayscaleBuffer(imagePath: string): Promise<Uint8Array>;
74
+ /** The drawn size of an image in its box, and where it sits there. */
29
75
  export interface FitResult {
30
76
  width: number;
31
77
  height: number;
32
78
  offsetX: number;
33
79
  offsetY: number;
34
80
  }
35
- export declare function applyContainFit(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number): FitResult;
36
- export declare function applyCoverFit(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number): FitResult;
81
+ /** Where an image smaller or larger than its box sits in it, per axis: 0 = start, 0.5 = center, 1 = end. */
82
+ export interface FitAlign {
83
+ x: number;
84
+ y: number;
85
+ }
86
+ /** Scaled to fit inside the box, whole image visible. */
87
+ export declare function applyContainFit(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number, align?: FitAlign): FitResult;
88
+ /** Scaled to cover the box, the overhang cut off. */
89
+ export declare function applyCoverFit(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number, align?: FitAlign): FitResult;
37
90
  export declare function applyFillFit(containerWidth: number, containerHeight: number): FitResult;
38
- export declare function applyFitNone(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number): {
39
- width: number;
40
- height: number;
41
- offsetX: number;
42
- offsetY: number;
43
- };
44
- export {};
91
+ /** At its own size (`naturalSize`), the overhang cut off. */
92
+ export declare function applyFitNone(imageWidth: number, imageHeight: number, containerWidth: number, containerHeight: number, align?: FitAlign): FitResult;
@@ -8,31 +8,198 @@ DataView.prototype.getUint24 = function (byteOffset, littleEndian = false) {
8
8
  const b3 = this.getUint8(byteOffset + 2);
9
9
  return littleEndian ? (b3 << 16) | (b2 << 8) | b1 : (b1 << 16) | (b2 << 8) | b3;
10
10
  };
11
- export async function getImageDimensions(buffer) {
12
- const dataView = new DataView(buffer.buffer);
13
- // Check for JPEG (0xFFD8 is the start of JPEG file)
14
- if (dataView.getUint16(0) === 0xffd8) {
15
- let offset = 2;
16
- while (offset < buffer.byteLength) {
17
- const marker = dataView.getUint16(offset, false);
18
- offset += 2;
19
- if (marker === 0xffc0 || marker === 0xffc2) {
20
- // SOF0 or SOF2
21
- return {
22
- height: dataView.getUint16(offset + 3, false),
23
- width: dataView.getUint16(offset + 5, false),
24
- };
25
- }
26
- else {
27
- offset += dataView.getUint16(offset, false);
28
- }
11
+ /**
12
+ * The matrix that turns the image's unit square upright, inside it - PDF's image space, y up, the first
13
+ * stored row at the top. Applied before the placement scales it, so the box stays the box.
14
+ */
15
+ export const ORIENTATION_MATRIX = {
16
+ 2: [-1, 0, 0, 1, 1, 0],
17
+ 3: [-1, 0, 0, -1, 1, 1],
18
+ 4: [1, 0, 0, -1, 0, 1],
19
+ 5: [0, -1, -1, 0, 1, 1],
20
+ 6: [0, -1, 1, 0, 0, 1],
21
+ 7: [0, 1, 1, 0, 0, 0],
22
+ 8: [0, 1, -1, 0, 1, 0],
23
+ };
24
+ /** The resolution of a file that declares none: CSS's reference pixel, 1/96 inch. */
25
+ export const DEFAULT_DPI = 96;
26
+ /**
27
+ * An image's own size in points, upright: its pixels at the resolution the file declares, else 96 dpi,
28
+ * turned by its orientation.
29
+ */
30
+ export function naturalSize(dims) {
31
+ const width = (dims.width * 72) / (dims.dpi?.x ?? DEFAULT_DPI);
32
+ const height = (dims.height * 72) / (dims.dpi?.y ?? DEFAULT_DPI);
33
+ return (dims.orientation ?? 1) >= 5 ? { width: height, height: width } : { width, height };
34
+ }
35
+ /** A resolution only counts if both axes are real numbers above zero; anything else is no declaration. */
36
+ function resolution(x, y) {
37
+ return Number.isFinite(x) && Number.isFinite(y) && x > 0 && y > 0 ? { x, y } : undefined;
38
+ }
39
+ /** PNG `pHYs`: pixels per unit, and only unit 1 (meter) is a physical size - 0 is an aspect ratio. */
40
+ function pngResolution(view) {
41
+ for (let offset = 8; offset + 8 <= view.byteLength;) {
42
+ const length = view.getUint32(offset);
43
+ const type = view.getUint32(offset + 4);
44
+ if (type === 0x49444154)
45
+ return undefined; // IDAT: pHYs must come before it
46
+ if (type === 0x70485973 && length >= 9 && offset + 17 <= view.byteLength) {
47
+ if (view.getUint8(offset + 16) !== 1)
48
+ return undefined;
49
+ return resolution(view.getUint32(offset + 8) * 0.0254, view.getUint32(offset + 12) * 0.0254);
50
+ }
51
+ offset += 12 + length;
52
+ }
53
+ return undefined;
54
+ }
55
+ /** JFIF `APP0`: units 1 = dots per inch, 2 = per centimeter, 0 = an aspect ratio only. */
56
+ function jfifResolution(view, data) {
57
+ if (view.getUint32(data) !== 0x4a464946 || view.getUint8(data + 4) !== 0)
58
+ return undefined; // "JFIF\0"
59
+ const units = view.getUint8(data + 7);
60
+ const scale = units === 1 ? 1 : units === 2 ? 2.54 : 0;
61
+ if (scale === 0)
62
+ return undefined;
63
+ return resolution(view.getUint16(data + 8) * scale, view.getUint16(data + 10) * scale);
64
+ }
65
+ /** The first IFD of a TIFF block (EXIF), as tag → the entry's offset in `view`. */
66
+ function tiffEntries(view, tiff) {
67
+ const le = view.getUint16(tiff) === 0x4949; // "II"
68
+ const ifd = tiff + view.getUint32(tiff + 4, le);
69
+ const entries = new Map();
70
+ const count = view.getUint16(ifd, le);
71
+ for (let i = 0; i < count; i++) {
72
+ const entry = ifd + 2 + i * 12;
73
+ entries.set(view.getUint16(entry, le), entry);
74
+ }
75
+ return { entries, le };
76
+ }
77
+ /**
78
+ * EXIF `APP1`: XResolution / YResolution (rationals) in ResolutionUnit, 2 = inch (the default), 3 = cm;
79
+ * Orientation 1-8.
80
+ */
81
+ function exifInfo(view, data) {
82
+ if (view.getUint32(data) !== 0x45786966 || view.getUint16(data + 4) !== 0)
83
+ return undefined; // "Exif\0\0"
84
+ const tiff = data + 6;
85
+ const { entries, le } = tiffEntries(view, tiff);
86
+ const rational = (tag) => {
87
+ const entry = entries.get(tag);
88
+ if (entry === undefined)
89
+ return NaN;
90
+ const at = tiff + view.getUint32(entry + 8, le);
91
+ return view.getUint32(at, le) / view.getUint32(at + 4, le);
92
+ };
93
+ const short = (tag) => {
94
+ const entry = entries.get(tag);
95
+ return entry === undefined ? undefined : view.getUint16(entry + 8, le);
96
+ };
97
+ const unit = short(0x0128) ?? 2;
98
+ const scale = unit === 2 ? 1 : unit === 3 ? 2.54 : 0;
99
+ const dpi = scale === 0 ? undefined : resolution(rational(0x011a) * scale, rational(0x011b) * scale);
100
+ const turn = short(0x0112);
101
+ const orientation = turn !== undefined && turn >= 2 && turn <= 8 ? turn : undefined;
102
+ return { ...(dpi ? { dpi } : {}), ...(orientation ? { orientation } : {}) };
103
+ }
104
+ /** What a header declares is metadata: a malformed declaration is none, never a failed image. */
105
+ function tryRead(read) {
106
+ try {
107
+ return read();
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ }
113
+ /** Start-of-frame markers; the rest of 0xFFC0..0xFFCF are tables (DHT C4, JPG C8, DAC CC). */
114
+ const isSof = (marker) => marker >= 0xffc0 &&
115
+ marker <= 0xffcf &&
116
+ marker !== 0xffc4 &&
117
+ marker !== 0xffc8 &&
118
+ marker !== 0xffcc;
119
+ /** Markers without a length: TEM and the restart markers. */
120
+ const isStandalone = (marker) => marker === 0xff01 || (marker >= 0xffd0 && marker <= 0xffd7);
121
+ function jpegHeader(view) {
122
+ // JFIF is the container's own declaration and wins; EXIF is what a camera writes.
123
+ let jfif;
124
+ let exif;
125
+ let adobe = false;
126
+ let offset = 2;
127
+ while (offset + 4 <= view.byteLength) {
128
+ if (view.getUint8(offset + 1) === 0xff) {
129
+ offset++; // a fill byte before the marker
130
+ continue;
131
+ }
132
+ const marker = view.getUint16(offset);
133
+ offset += 2;
134
+ if (isStandalone(marker))
135
+ continue;
136
+ if (isSof(marker)) {
137
+ const dpi = jfif ?? exif?.dpi;
138
+ const orientation = exif?.orientation;
139
+ return {
140
+ height: view.getUint16(offset + 3),
141
+ width: view.getUint16(offset + 5),
142
+ components: view.getUint8(offset + 7),
143
+ sof: marker,
144
+ adobe,
145
+ ...(dpi ? { dpi } : {}),
146
+ ...(orientation ? { orientation } : {}),
147
+ };
29
148
  }
149
+ if (marker === 0xffe0)
150
+ jfif ?? (jfif = tryRead(() => jfifResolution(view, offset + 2)));
151
+ if (marker === 0xffe1)
152
+ exif ?? (exif = tryRead(() => exifInfo(view, offset + 2)));
153
+ // "Adobe"
154
+ if (marker === 0xffee &&
155
+ view.getUint32(offset + 2) === 0x41646f62 &&
156
+ view.getUint8(offset + 6) === 0x65)
157
+ adobe = true;
158
+ offset += view.getUint16(offset);
159
+ }
160
+ throw new Error("Unsupported image format");
161
+ }
162
+ /** Thrown for a JPEG we cannot embed as it is. Says what it is and what to do. */
163
+ export class JpegFormatError extends Error {
164
+ constructor(message) {
165
+ super(`@jasy/pdf: ${message}`);
166
+ }
167
+ }
168
+ /** Baseline, extended sequential and progressive: the codings a PDF viewer's DCTDecode reads. */
169
+ const DCT_CODINGS = new Set([0xffc0, 0xffc1, 0xffc2]);
170
+ /** The color of a JPEG as it is embedded (`DCTDecode`): the space it was saved in. */
171
+ export function jpegColor(bytes) {
172
+ const header = jpegHeader(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
173
+ if (!DCT_CODINGS.has(header.sof)) {
174
+ throw new JpegFormatError("this JPEG is lossless, hierarchical or arithmetic-coded, which PDF viewers cannot decode. " +
175
+ "Save it again as a baseline or progressive JPEG, or as a PNG.");
176
+ }
177
+ switch (header.components) {
178
+ case 1:
179
+ return { space: "gray" };
180
+ case 3:
181
+ return { space: "rgb" };
182
+ case 4:
183
+ return header.adobe ? { space: "cmyk", inverted: true } : { space: "cmyk" };
184
+ default:
185
+ throw new JpegFormatError(`this JPEG has ${header.components} color channels; only gray, RGB and CMYK are embedded.`);
186
+ }
187
+ }
188
+ /** Read from the header alone, synchronously - layout can ask it. */
189
+ export function readImageDimensions(buffer) {
190
+ // The view's own window: a `Uint8Array` may be a slice of a larger buffer (a Node `Buffer` usually is).
191
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
192
+ if (dataView.getUint16(0) === 0xffd8) {
193
+ const { width, height, dpi, orientation } = jpegHeader(dataView);
194
+ return { width, height, ...(dpi ? { dpi } : {}), ...(orientation ? { orientation } : {}) };
30
195
  }
31
196
  // Check for PNG (0x89504E47 is the PNG signature)
32
197
  if (dataView.getUint32(0) === 0x89504e47) {
198
+ const dpi = tryRead(() => pngResolution(dataView));
33
199
  return {
34
200
  width: dataView.getUint32(16, false),
35
201
  height: dataView.getUint32(20, false),
202
+ ...(dpi ? { dpi } : {}),
36
203
  };
37
204
  }
38
205
  // Check for BMP (0x424D is the BMP signature)
@@ -122,75 +289,36 @@ export async function convertImageToGrayscaleBuffer(imagePath) {
122
289
  const grayscaleBuffer = await image.getBuffer(mimeType);
123
290
  return grayscaleBuffer;
124
291
  }
125
- export function applyContainFit(imageWidth, imageHeight, containerWidth, containerHeight) {
126
- const imageAspectRatio = imageWidth / imageHeight;
127
- const containerAspectRatio = containerWidth / containerHeight;
128
- let width, height, offsetX, offsetY;
129
- if (imageAspectRatio > containerAspectRatio) {
130
- // The images width is bigger than the containers width
131
- const scaleFactor = containerWidth / imageWidth;
132
- width = containerWidth;
133
- height = imageHeight * scaleFactor;
134
- offsetX = 0;
135
- offsetY = (containerHeight - height) / 2; // Center vertically
136
- }
137
- else {
138
- // THe images height is bigge rthan the containers height
139
- const scaleFactor = containerHeight / imageHeight;
140
- width = imageWidth * scaleFactor;
141
- height = containerHeight;
142
- offsetX = (containerWidth - width) / 2; // Center horizontally
143
- offsetY = 0;
144
- }
292
+ const CENTER = { x: 0.5, y: 0.5 };
293
+ /** The one place a fitted image is positioned: what is left over (or overhangs) is split by `align`. */
294
+ function placed(width, height, containerWidth, containerHeight, align) {
145
295
  return {
146
296
  width,
147
297
  height,
148
- offsetX,
149
- offsetY,
298
+ offsetX: (containerWidth - width) * align.x,
299
+ offsetY: (containerHeight - height) * align.y,
150
300
  };
151
301
  }
152
- export function applyCoverFit(imageWidth, imageHeight, containerWidth, containerHeight) {
153
- const imageAspectRatio = imageWidth / imageHeight;
154
- const containerAspectRatio = containerWidth / containerHeight;
155
- let width, height, offsetX, offsetY;
156
- if (imageAspectRatio > containerAspectRatio) {
157
- // The images width is bigger than containers width
158
- const scaleFactor = containerHeight / imageHeight;
159
- width = imageWidth * scaleFactor;
160
- height = containerHeight;
161
- offsetX = (containerWidth - width) / 2; // Center horizontally
162
- offsetY = 0;
163
- }
164
- else {
165
- // The images height is bigger than containers height
166
- const scaleFactor = containerWidth / imageWidth;
167
- width = containerWidth;
168
- height = imageHeight * scaleFactor;
169
- offsetX = 0;
170
- offsetY = (containerHeight - height) / 2; // Center vertically
171
- }
172
- return {
173
- width,
174
- height,
175
- offsetX,
176
- offsetY,
177
- };
302
+ /** Scaled by the width's or the height's factor; that axis takes the box's extent exactly. */
303
+ function scaled(imageWidth, imageHeight, containerWidth, containerHeight, align, byWidthWins) {
304
+ const byWidth = containerWidth / imageWidth;
305
+ const byHeight = containerHeight / imageHeight;
306
+ return byWidthWins(byWidth, byHeight)
307
+ ? placed(containerWidth, imageHeight * byWidth, containerWidth, containerHeight, align)
308
+ : placed(imageWidth * byHeight, containerHeight, containerWidth, containerHeight, align);
309
+ }
310
+ /** Scaled to fit inside the box, whole image visible. */
311
+ export function applyContainFit(imageWidth, imageHeight, containerWidth, containerHeight, align = CENTER) {
312
+ return scaled(imageWidth, imageHeight, containerWidth, containerHeight, align, (w, h) => w < h);
313
+ }
314
+ /** Scaled to cover the box, the overhang cut off. */
315
+ export function applyCoverFit(imageWidth, imageHeight, containerWidth, containerHeight, align = CENTER) {
316
+ return scaled(imageWidth, imageHeight, containerWidth, containerHeight, align, (w, h) => w >= h);
178
317
  }
179
318
  export function applyFillFit(containerWidth, containerHeight) {
180
- return {
181
- width: containerWidth,
182
- height: containerHeight,
183
- offsetX: 0,
184
- offsetY: 0,
185
- };
319
+ return { width: containerWidth, height: containerHeight, offsetX: 0, offsetY: 0 };
186
320
  }
187
- export function applyFitNone(imageWidth, imageHeight, containerWidth, containerHeight) {
188
- const offsetX = (containerWidth - imageWidth) / 2; // Center horizontally
189
- const offsetY = (containerHeight - imageHeight) / 2; // Center vertically
190
- return {
191
- width: imageWidth, // Hold orignal image size
192
- height: imageHeight,
193
- offsetX: offsetX > 0 ? offsetX : 0, // Dont move outside the container...
194
- offsetY: offsetY > 0 ? offsetY : 0,
195
- };
321
+ /** At its own size (`naturalSize`), the overhang cut off. */
322
+ export function applyFitNone(imageWidth, imageHeight, containerWidth, containerHeight, align = CENTER) {
323
+ return placed(imageWidth, imageHeight, containerWidth, containerHeight, align);
196
324
  }
@@ -12,6 +12,8 @@ import { DestRegistry } from "./dest-registry.js";
12
12
  import { AcroFormCollector } from "../forms/acroform.js";
13
13
  import type { PDFConfig } from "../renderer/pdf-document-class.js";
14
14
  import type { FontMetrics } from "./font-metrics.js";
15
+ import { Diagnostics } from "../common/diagnostics.js";
16
+ import { type ImageColor } from "./image-helper.js";
15
17
  interface FontIndexes {
16
18
  fontIndex: number;
17
19
  resourceIndex: number;
@@ -44,8 +46,8 @@ export declare class PDFObjectManager implements FontMetrics {
44
46
  /** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
45
47
  * key would carry the whole run's text, so every lookup would copy it into a fresh string. */
46
48
  private shapeCache;
47
- /** Bumped by every font registration; `runAdvance` watches it to drop memoised widths, since a
48
- * family name can mean a different face afterwards. */
49
+ /** Bumped when a face is really ADDED (a repeat registration returns early, untouched); every
50
+ * memoised measurement watches it, since a family name can mean a different face afterwards. */
49
51
  fontEpoch: number;
50
52
  private shapedOrigins;
51
53
  private attachments;
@@ -78,7 +80,9 @@ export declare class PDFObjectManager implements FontMetrics {
78
80
  * character went missing instead of a server log nobody reads.
79
81
  */
80
82
  private readonly missingGlyphs;
81
- reportMissingGlyph(codePoints: number[]): void;
83
+ reportMissingGlyph(codePoints: number[], about?: object, page?: number): void;
84
+ /** The render's diagnostics channel (`onDiagnostic`); see `common/diagnostics.ts`. */
85
+ readonly diagnostics: Diagnostics;
82
86
  /** The dropped code points, in the order first seen. Empty for a document that hit none. */
83
87
  getMissingGlyphs(): number[];
84
88
  setOverflowPolicy(policy: OverflowPolicy): void;
@@ -97,6 +101,11 @@ export declare class PDFObjectManager implements FontMetrics {
97
101
  * A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
98
102
  */
99
103
  pdfString(text: string): string;
104
+ /**
105
+ * A URI: 7-bit ASCII by definition, so anything else is percent-encoded as UTF-8 - the way a browser
106
+ * writes the link - rather than squeezed into a text encoding.
107
+ */
108
+ pdfUri(uri: string): string;
100
109
  private streamPayload;
101
110
  private stream;
102
111
  addContentStream(content: string): number;
@@ -107,7 +116,7 @@ export declare class PDFObjectManager implements FontMetrics {
107
116
  private getCurrentByteLength;
108
117
  setParentObjectNumber(number: number): void;
109
118
  getParentObjectNumber(): number;
110
- registerImage(width: number, height: number, imageType: string, imageData: string, smaskData?: string): number;
119
+ registerImage(width: number, height: number, imageType: string, imageData: string, smaskData?: string, color?: ImageColor): number;
111
120
  attachFile(name: string, data: Uint8Array, opts?: {
112
121
  relationship?: string;
113
122
  mimeType?: string;
@@ -159,7 +168,7 @@ export declare class PDFObjectManager implements FontMetrics {
159
168
  */
160
169
  hasGlyph(codePoint: number, fontFamily: string, fontStyle?: FontStyle): boolean;
161
170
  /**
162
- * Whether the document draws this code point through the colour-emoji path instead of the text
171
+ * Whether the document draws this code point through the color-emoji path instead of the text
163
172
  * font - a fallback emoji FONT, or an image source (`Document({ emoji })`). Such a code point is
164
173
  * missing from the text font ON PURPOSE, so glyph coverage must leave it alone. Mirrors exactly
165
174
  * what `getCharWidth` measures it as, or a dropped emoji would have been measured and not drawn.
@@ -199,7 +208,9 @@ export declare class PDFObjectManager implements FontMetrics {
199
208
  * `GPOS` tables are unparsed), so it returns zeros - never a wrong value, just no adjustment.
200
209
  */
201
210
  /** Kerning between adjacent GLYPHS - what a shaped run needs, and what the backend already holds.
202
- * Same lookup as the character path; `getKerning` is keyed by glyph id either way. */
211
+ * Same lookup as the character path; `getKerning` is keyed by glyph id either way. A pair that
212
+ * touches a space is never kerned, as on the character path: the line breaker measures words and
213
+ * spaces apart, so a pair across a space would be drawn but never measured. */
203
214
  getGlyphKernPairs(glyphs: readonly number[], fontFamily: string, fontStyle: FontStyle): number[];
204
215
  getKernPairs(text: string, fontFamily: string, fontStyle: FontStyle, ligatures?: boolean): number[];
205
216
  /**