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

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 (210) hide show
  1. package/dist/api/content.d.ts +5 -5
  2. package/dist/api/content.js +12 -6
  3. package/dist/api/font-url.d.ts +1 -2
  4. package/dist/api/font-url.js +8 -74
  5. package/dist/api/forms.d.ts +3 -0
  6. package/dist/api/forms.js +3 -0
  7. package/dist/api/index.d.ts +2 -0
  8. package/dist/api/index.js +1 -0
  9. package/dist/api/layout.d.ts +6 -0
  10. package/dist/api/layout.js +7 -2
  11. package/dist/api/page-builder.d.ts +4 -0
  12. package/dist/api/page-builder.js +4 -0
  13. package/dist/api/structure.d.ts +10 -2
  14. package/dist/api/structure.js +9 -6
  15. package/dist/api/table.d.ts +5 -3
  16. package/dist/api/table.js +99 -22
  17. package/dist/common/diagnostics.d.ts +53 -0
  18. package/dist/common/diagnostics.js +77 -0
  19. package/dist/edit/acroform-reader.js +7 -7
  20. package/dist/edit/appearance.js +3 -3
  21. package/dist/edit/attachments.d.ts +52 -0
  22. package/dist/edit/attachments.js +178 -0
  23. package/dist/edit/document.d.ts +38 -8
  24. package/dist/edit/document.js +208 -51
  25. package/dist/edit/fill.js +8 -23
  26. package/dist/edit/filters.d.ts +15 -0
  27. package/dist/edit/filters.js +157 -0
  28. package/dist/edit/index.d.ts +4 -1
  29. package/dist/edit/index.js +3 -1
  30. package/dist/edit/objects.d.ts +3 -4
  31. package/dist/edit/objects.js +5 -18
  32. package/dist/elements/canvas-element.d.ts +3 -0
  33. package/dist/elements/canvas-element.js +14 -3
  34. package/dist/elements/container-element.d.ts +5 -1
  35. package/dist/elements/container-element.js +35 -15
  36. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  37. package/dist/elements/forms/checkbox-element.js +12 -3
  38. package/dist/elements/forms/choice-element.d.ts +6 -1
  39. package/dist/elements/forms/choice-element.js +17 -6
  40. package/dist/elements/forms/field-font.d.ts +8 -0
  41. package/dist/elements/forms/field-font.js +5 -0
  42. package/dist/elements/forms/push-button-element.d.ts +6 -1
  43. package/dist/elements/forms/push-button-element.js +16 -4
  44. package/dist/elements/forms/radio-element.d.ts +2 -0
  45. package/dist/elements/forms/radio-element.js +12 -3
  46. package/dist/elements/forms/signature-element.d.ts +6 -1
  47. package/dist/elements/forms/signature-element.js +16 -4
  48. package/dist/elements/forms/text-field-element.d.ts +6 -1
  49. package/dist/elements/forms/text-field-element.js +16 -4
  50. package/dist/elements/image-element.d.ts +39 -1
  51. package/dist/elements/image-element.js +95 -12
  52. package/dist/elements/layout/anchor-element.d.ts +8 -10
  53. package/dist/elements/layout/anchor-element.js +10 -18
  54. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  55. package/dist/elements/layout/bookmark-element.js +11 -20
  56. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  57. package/dist/elements/layout/default-text-style-element.js +14 -31
  58. package/dist/elements/layout/deferred-element.d.ts +12 -4
  59. package/dist/elements/layout/deferred-element.js +24 -7
  60. package/dist/elements/layout/expanded-element.d.ts +7 -0
  61. package/dist/elements/layout/expanded-element.js +40 -18
  62. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  63. package/dist/elements/layout/keep-together-element.js +21 -45
  64. package/dist/elements/layout/link-element.d.ts +8 -13
  65. package/dist/elements/layout/link-element.js +9 -19
  66. package/dist/elements/layout/padding-element.d.ts +15 -1
  67. package/dist/elements/layout/padding-element.js +55 -27
  68. package/dist/elements/layout/page-break-element.d.ts +4 -1
  69. package/dist/elements/layout/page-break-element.js +6 -1
  70. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  71. package/dist/elements/layout/page-builder-element.js +11 -0
  72. package/dist/elements/layout/positioned-element.d.ts +14 -2
  73. package/dist/elements/layout/positioned-element.js +36 -10
  74. package/dist/elements/layout/proxy-element.d.ts +43 -0
  75. package/dist/elements/layout/proxy-element.js +94 -0
  76. package/dist/elements/layout/repeating-header-element.d.ts +6 -0
  77. package/dist/elements/layout/repeating-header-element.js +40 -13
  78. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  79. package/dist/elements/layout/rotated-box-element.js +27 -14
  80. package/dist/elements/layout/rotated-element.d.ts +7 -0
  81. package/dist/elements/layout/rotated-element.js +25 -2
  82. package/dist/elements/layout/struct-group.d.ts +9 -17
  83. package/dist/elements/layout/struct-group.js +10 -35
  84. package/dist/elements/line-element.d.ts +1 -0
  85. package/dist/elements/line-element.js +6 -0
  86. package/dist/elements/page-element.d.ts +10 -3
  87. package/dist/elements/page-element.js +39 -31
  88. package/dist/elements/pdf-document-element.d.ts +3 -0
  89. package/dist/elements/pdf-document-element.js +7 -0
  90. package/dist/elements/pdf-element.d.ts +75 -15
  91. package/dist/elements/pdf-element.js +68 -18
  92. package/dist/elements/rectangle-element.d.ts +14 -0
  93. package/dist/elements/rectangle-element.js +97 -52
  94. package/dist/elements/row-element.d.ts +11 -0
  95. package/dist/elements/row-element.js +58 -8
  96. package/dist/elements/svg-element.d.ts +30 -6
  97. package/dist/elements/svg-element.js +88 -17
  98. package/dist/elements/text-element.d.ts +32 -13
  99. package/dist/elements/text-element.js +119 -97
  100. package/dist/forms/acroform.d.ts +9 -1
  101. package/dist/forms/acroform.js +83 -34
  102. package/dist/forms/appearance.d.ts +17 -6
  103. package/dist/forms/appearance.js +18 -16
  104. package/dist/ir/display-list.d.ts +12 -2
  105. package/dist/layout/box-constraints.d.ts +5 -0
  106. package/dist/layout/box-constraints.js +14 -0
  107. package/dist/layout/collect-images.d.ts +6 -4
  108. package/dist/layout/collect-images.js +6 -4
  109. package/dist/layout/flex-item.d.ts +9 -0
  110. package/dist/layout/flex-item.js +39 -0
  111. package/dist/layout/fragmentation.d.ts +12 -4
  112. package/dist/layout/fragmentation.js +37 -14
  113. package/dist/layout/layout-cache.d.ts +53 -0
  114. package/dist/layout/layout-cache.js +107 -0
  115. package/dist/renderer/anchor-renderer.d.ts +2 -1
  116. package/dist/renderer/anchor-renderer.js +5 -4
  117. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  118. package/dist/renderer/bookmark-renderer.js +5 -4
  119. package/dist/renderer/canvas-renderer.d.ts +2 -1
  120. package/dist/renderer/canvas-renderer.js +5 -2
  121. package/dist/renderer/container-renderer.d.ts +2 -1
  122. package/dist/renderer/container-renderer.js +6 -5
  123. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  124. package/dist/renderer/default-text-style-renderer.js +2 -3
  125. package/dist/renderer/deferred-renderer.d.ts +2 -1
  126. package/dist/renderer/deferred-renderer.js +2 -3
  127. package/dist/renderer/expanded-renderer.d.ts +2 -1
  128. package/dist/renderer/expanded-renderer.js +2 -3
  129. package/dist/renderer/form-field-renderer.d.ts +2 -1
  130. package/dist/renderer/form-field-renderer.js +16 -3
  131. package/dist/renderer/image-renderer.d.ts +2 -1
  132. package/dist/renderer/image-renderer.js +5 -2
  133. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  134. package/dist/renderer/keep-together-renderer.js +2 -3
  135. package/dist/renderer/line-renderer.d.ts +2 -1
  136. package/dist/renderer/line-renderer.js +7 -2
  137. package/dist/renderer/link-renderer.d.ts +2 -1
  138. package/dist/renderer/link-renderer.js +36 -5
  139. package/dist/renderer/padding-renderer.d.ts +2 -1
  140. package/dist/renderer/padding-renderer.js +5 -5
  141. package/dist/renderer/page-break-renderer.d.ts +6 -5
  142. package/dist/renderer/page-break-renderer.js +14 -7
  143. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  144. package/dist/renderer/page-builder-renderer.js +2 -3
  145. package/dist/renderer/page-renderer.js +28 -7
  146. package/dist/renderer/pdf-backend.d.ts +4 -3
  147. package/dist/renderer/pdf-backend.js +5 -5
  148. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  149. package/dist/renderer/pdf-document-renderer.js +15 -16
  150. package/dist/renderer/pdf-renderer.js +11 -11
  151. package/dist/renderer/positioned-renderer.d.ts +6 -1
  152. package/dist/renderer/positioned-renderer.js +6 -5
  153. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  154. package/dist/renderer/rectangle-renderer.js +9 -4
  155. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  156. package/dist/renderer/repeating-header-renderer.js +6 -5
  157. package/dist/renderer/rotated-renderer.d.ts +2 -1
  158. package/dist/renderer/rotated-renderer.js +8 -5
  159. package/dist/renderer/row-renderer.d.ts +2 -1
  160. package/dist/renderer/row-renderer.js +6 -5
  161. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  162. package/dist/renderer/struct-group-renderer.js +2 -3
  163. package/dist/renderer/svg-renderer.d.ts +2 -1
  164. package/dist/renderer/svg-renderer.js +8 -2
  165. package/dist/renderer/text-renderer.d.ts +4 -6
  166. package/dist/renderer/text-renderer.js +101 -111
  167. package/dist/svg/gradients.js +5 -0
  168. package/dist/svg/index.js +10 -1
  169. package/dist/svg/style.d.ts +2 -0
  170. package/dist/svg/style.js +11 -0
  171. package/dist/text/advance.d.ts +4 -0
  172. package/dist/text/advance.js +17 -4
  173. package/dist/text/bidi.d.ts +0 -2
  174. package/dist/text/bidi.js +0 -10
  175. package/dist/text/glyph-coverage.d.ts +1 -1
  176. package/dist/text/glyph-coverage.js +7 -3
  177. package/dist/text/hyphen-breaks.d.ts +17 -0
  178. package/dist/text/hyphen-breaks.js +54 -0
  179. package/dist/text/line-breaker.d.ts +34 -31
  180. package/dist/text/line-breaker.js +364 -176
  181. package/dist/text/line-metrics.d.ts +6 -3
  182. package/dist/text/line-metrics.js +7 -2
  183. package/dist/text/pdf-text-string.d.ts +21 -0
  184. package/dist/text/pdf-text-string.js +114 -0
  185. package/dist/text/text-decoration.d.ts +5 -5
  186. package/dist/text/text-decoration.js +5 -5
  187. package/dist/text/word-splitting.d.ts +15 -2
  188. package/dist/text/word-splitting.js +34 -8
  189. package/dist/utils/afm-parser.d.ts +2 -0
  190. package/dist/utils/afm-parser.js +10 -1
  191. package/dist/utils/dest-registry.d.ts +1 -1
  192. package/dist/utils/dest-registry.js +3 -2
  193. package/dist/utils/fetch-bytes.d.ts +19 -0
  194. package/dist/utils/fetch-bytes.js +79 -0
  195. package/dist/utils/flex-layout.d.ts +13 -13
  196. package/dist/utils/flex-layout.js +318 -216
  197. package/dist/utils/font-metrics.d.ts +4 -3
  198. package/dist/utils/image-helper.js +2 -1
  199. package/dist/utils/pdf-object-manager.d.ts +11 -3
  200. package/dist/utils/pdf-object-manager.js +39 -13
  201. package/dist/utils/renderer-registry.d.ts +43 -2
  202. package/dist/utils/renderer-registry.js +40 -0
  203. package/dist/utils/struct-tree.d.ts +24 -0
  204. package/dist/utils/struct-tree.js +61 -9
  205. package/dist/utils/ttf-parser.d.ts +4 -0
  206. package/dist/utils/ttf-parser.js +27 -4
  207. package/dist/validators/element-validator.js +2 -1
  208. package/package.json +1 -1
  209. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  210. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -1,5 +1,9 @@
1
- import { FlexiblePDFElement } from "../elements/pdf-element.js";
1
+ import { alignSelfOf, flexibleOf, flexShrinkOf, orderOf } from "../layout/flex-item.js";
2
2
  import { BoxConstraints } from "../layout/box-constraints.js";
3
+ const sameConstraints = (a, b) => a.minWidth === b.minWidth &&
4
+ a.maxWidth === b.maxWidth &&
5
+ a.minHeight === b.minHeight &&
6
+ a.maxHeight === b.maxHeight;
3
7
  export const VERTICAL_AXIS = {
4
8
  mainHorizontal: false,
5
9
  mainOf: (s) => s.height,
@@ -33,21 +37,24 @@ function crossOffset(align, crossExtent, childCross) {
33
37
  * container where nobody sets one comes back with the identical array.
34
38
  */
35
39
  function inLayoutOrder(children, reverse) {
36
- const ordered = children.some((c) => c.order !== 0)
37
- ? [...children].sort((a, b) => a.order - b.order)
40
+ const ordered = children.some((c) => orderOf(c) !== 0)
41
+ ? [...children].sort((a, b) => orderOf(a) - orderOf(b))
38
42
  : children;
39
43
  return reverse ? [...ordered].reverse() : ordered;
40
44
  }
45
+ /**
46
+ * The main-axis alignment as the container means it. Reversed, the axis runs from the other end: `start`
47
+ * is where the first child of the REVERSED order sits - the right of a Row, the bottom of a Column (CSS
48
+ * `row-reverse` with `flex-start`, Flutter's `MainAxisAlignment.start` on a reversed axis). `center`,
49
+ * `between` and `around` are symmetric and stay.
50
+ */
51
+ function mainAlignOf(options) {
52
+ const main = options.main ?? "start";
53
+ if (!options.reverse)
54
+ return main;
55
+ return main === "start" ? "end" : main === "end" ? "start" : main;
56
+ }
41
57
  export class FlexLayoutHelper {
42
- /**
43
- * Lays out a flex line along `axis`, IN SOURCE ORDER, and places every child.
44
- * Fixed children take their natural main extent; flex (`ExpandedElement`) children
45
- * split the leftover main space by their `flex`. `gap` is inserted between children.
46
- * `main` distributes any leftover when there is no flex child; `cross` positions/sizes
47
- * each child across the line. Returns the total main extent consumed and the cross
48
- * extent occupied. Vertical with `gap 0`, `main start`, `cross stretch` reproduces the
49
- * previous Column layout exactly.
50
- */
51
58
  /**
52
59
  * Lays out the children along `axis`, on one line or several.
53
60
  *
@@ -55,12 +62,12 @@ export class FlexLayoutHelper {
55
62
  * straight through, which is what keeps every existing document byte-identical. With `wrap` the
56
63
  * children are split into lines first and the SAME engine runs once per line.
57
64
  */
58
- static layout(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
65
+ static layout(children, axis, mainAvail, crossAvail, options, ctx, mode = "layout") {
59
66
  if (!options.wrap || children.length === 0 || !Number.isFinite(mainAvail)) {
60
67
  // An unbounded main axis has no edge to wrap at, so wrapping there is meaningless, not an error.
61
- return FlexLayoutHelper.layoutLine(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx);
68
+ return FlexLayoutHelper.layoutLine(children, axis, mainAvail, crossAvail, 0, options, ctx, mode);
62
69
  }
63
- return FlexLayoutHelper.layoutWrapped(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx);
70
+ return FlexLayoutHelper.layoutWrapped(children, axis, mainAvail, crossAvail, options, ctx, mode);
64
71
  }
65
72
  /**
66
73
  * The wrapping path: split into lines, lay each one out, then place the lines across the axis.
@@ -69,21 +76,19 @@ export class FlexLayoutHelper {
69
76
  * is the same measure-then-place shape the single-line engine already uses, and it is what
70
77
  * `alignContent` needs: the block of lines can only be centred once its total is known.
71
78
  */
72
- static layoutWrapped(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
79
+ static layoutWrapped(children, axis, mainAvail, crossAvail, options, ctx, mode) {
73
80
  const gap = options.gap ?? 0;
74
81
  const ordered = inLayoutOrder(children, options.reverse ?? false);
75
- // Which children share a line. A child is measured at its natural main extent; a flex child
76
- // contributes its BASIS, which is 0 unless it asked for one - so it never forces a break by itself.
77
- // A child's natural main extent. A PERCENTAGE child has no fixed answer: its size depends on how
78
- // many gaps the line ends up with, which depends on who is on it. So it is asked again for every
79
- // candidate membership below rather than measured once.
80
- // A plain child's extent does not depend on the line, so it is laid out ONCE and remembered.
81
- // Without this each membership probe re-lays every child already on the line - O(n^2) subtree
82
- // layouts, which is expensive the moment those children hold a Table or a wrapping paragraph.
82
+ // Which children share a line. A child counts with its natural main extent; a flex child with its
83
+ // BASIS, which is 0 unless it asked for one - so it never forces a break by itself. A PERCENTAGE
84
+ // child's size depends on how many gaps the line ends up with, so it is asked again for every
85
+ // candidate membership. A plain child's extent does not, so it is measured ONCE and remembered -
86
+ // otherwise each membership probe re-lays every child already on the line (O(n^2) subtree layouts).
83
87
  const measured = new Map();
84
88
  const naturalMain = (child, base) => {
85
- if (child instanceof FlexiblePDFElement)
86
- return child.getBasis(base);
89
+ const flexible = flexibleOf(child);
90
+ if (flexible)
91
+ return flexible.getBasis(base);
87
92
  const factor = child.relativeSizeFactor(axis.mainHorizontal);
88
93
  if (factor !== undefined)
89
94
  return base * factor;
@@ -92,9 +97,9 @@ export class FlexLayoutHelper {
92
97
  return cached;
93
98
  // Measured UNCAPPED, the same way the single-line engine measures a plain child. Handing the
94
99
  // line width in as a cap would silently squash a child wider than the line - CSS lets such a
95
- // child overflow, and so does our own non-wrapping path.
96
- const needsBound = child.needsBoundedMain(axis.mainHorizontal);
97
- const main = axis.mainOf(child.calculateLayout(axis.measureConstraints(crossAvail, needsBound ? mainAvail : Infinity), axis.offsetAt(mainStart, crossOrigin), ctx));
100
+ // child overflow, and so does our own non-wrapping path. A stack holding a Spacer counts with its
101
+ // content too: its Spacer only grows into what is left once the line is laid out.
102
+ const main = axis.mainOf(child.measure(axis.measureConstraints(crossAvail, Infinity), ctx));
98
103
  measured.set(child, main);
99
104
  return main;
100
105
  };
@@ -117,16 +122,19 @@ export class FlexLayoutHelper {
117
122
  }
118
123
  // Each line keeps SOURCE order internally: the ordering was applied above, and applying it again
119
124
  // per line would reverse twice.
120
- const lineOptions = { ...options, wrap: false, reverse: false };
121
- const measure = lines.map((line) => FlexLayoutHelper.layoutLine(line, axis, mainAvail, Infinity, // let each line report the cross extent it actually needs
122
- mainStart, crossOrigin, lineOptions, ctx));
125
+ const lineOptions = { ...options, wrap: false, reverse: false, main: mainAlignOf(options) };
126
+ const measure = lines.map((line) => FlexLayoutHelper.layoutLine(line, axis, mainAvail,
127
+ // Across a Column a child gets the column's width, as a block does in CSS (a paragraph wraps
128
+ // inside it); across a Row it is as tall as its content. Either way the line is only as big as
129
+ // what is on it, so `stretch` fills the line and not the container.
130
+ axis.mainHorizontal ? Infinity : crossAvail, 0, lineOptions, ctx, "measure", true));
123
131
  const heights = measure.map((m) => m.crossUsed);
124
132
  const totalCross = heights.reduce((n, h) => n + h, 0) + Math.max(0, lines.length - 1) * gap;
125
133
  // `alignContent` distributes the BLOCK of lines across the axis, using the same vocabulary the main
126
134
  // axis uses. Only meaningful in a bounded cross axis with room left over.
127
135
  const slack = Number.isFinite(crossAvail) ? crossAvail - totalCross : 0;
128
136
  const align = options.alignContent ?? "start";
129
- let cursor = crossOrigin;
137
+ let cursor = 0;
130
138
  let between = gap;
131
139
  if (slack > 0) {
132
140
  if (align === "center")
@@ -143,203 +151,297 @@ export class FlexLayoutHelper {
143
151
  }
144
152
  lines.forEach((line, i) => {
145
153
  FlexLayoutHelper.layoutLine(line, axis, mainAvail, heights[i], // the line's own extent, so `stretch` fills the line and not the container
146
- mainStart, cursor, lineOptions, ctx);
154
+ cursor, lineOptions, ctx, mode);
147
155
  cursor += heights[i];
148
156
  if (i < lines.length - 1)
149
157
  cursor += between;
150
158
  });
151
- return { mainUsed: mainAvail, crossUsed: Math.max(totalCross, cursor - crossOrigin) };
159
+ return { mainUsed: mainAvail, crossUsed: Math.max(totalCross, cursor) };
152
160
  }
153
- static layoutLine(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
161
+ /**
162
+ * Lays out ONE flex line along `axis` and places every child. Fixed children take their natural main
163
+ * extent; flex children split the leftover by their `flex`; `gap` goes between children. `main`
164
+ * distributes leftover when nothing flexes; `cross` positions or stretches each child across the line.
165
+ * Returns the main extent consumed and the cross extent occupied.
166
+ *
167
+ * The steps run in this order, and the order is part of the result: `leftover` is taken after the
168
+ * shrink and before the share, and the main-axis spacing reads exactly that value.
169
+ */
170
+ static layoutLine(children, axis, mainAvail, crossAvail, crossOrigin, options, ctx, mode,
171
+ // The line's cross extent is its content, not `crossAvail`: one line of a wrapping container.
172
+ crossFromContent = false) {
154
173
  const gap = options.gap ?? 0;
155
- const main = options.main ?? "start";
156
- const cross = options.cross ?? "stretch";
157
- children = inLayoutOrder(children, options.reverse ?? false);
158
- const count = children.length;
159
- const totalGap = Math.max(0, count - 1) * gap;
160
- // The main extent a percentage child resolves against: the line's own extent minus the gaps
161
- // between the items. So N children at (100/N)% + gaps fit exactly, instead of overflowing by the
162
- // gaps (the flexbox `width: 33%` + gap trap). Only finite when the line's main axis is bounded -
163
- // a fraction of an unbounded main axis has no meaning, so `%` children fall back to shrink-wrap.
164
- const percentBase = mainAvail !== Infinity ? Math.max(0, mainAvail - totalGap) : Infinity;
165
- // The main cap to hand a child: `percentBase` for a percentage child (so its fraction resolves) and
166
- // for a child that cannot lay itself out without a bound (a nested stack holding an `Expanded`/
167
- // `Spacer`); unbounded for everyone else, who keep their natural size and never fill the line.
168
- // Filled by the shrink pass below; read by `mainCapFor` in pass 2. Declared here so the closure
169
- // can see it - it is empty for every document that does not ask for shrinking.
170
- const shrunkCap = new Map();
171
- const mainCapFor = (child) => {
172
- const target = shrunkCap.get(child);
173
- if (target !== undefined)
174
- return target;
175
- if (percentBase === Infinity)
176
- return Infinity;
177
- const needsBound = child.relativeSizeFactor(axis.mainHorizontal) !== undefined ||
178
- child.needsBoundedMain(axis.mainHorizontal);
179
- return needsBound ? percentBase : Infinity;
174
+ const ordered = inLayoutOrder(children, options.reverse ?? false);
175
+ const totalGap = Math.max(0, ordered.length - 1) * gap;
176
+ const line = {
177
+ children: ordered,
178
+ axis,
179
+ mainAvail,
180
+ crossAvail,
181
+ ctx,
182
+ totalGap,
183
+ // The main extent a percentage child resolves against: the line minus the gaps between its items,
184
+ // so N children at (100/N)% + gaps fit exactly (the flexbox `width: 33%` + gap trap). Only finite
185
+ // on a bounded main axis - a fraction of an unbounded one has no meaning, so `%` shrink-wraps.
186
+ percentBase: mainAvail !== Infinity ? Math.max(0, mainAvail - totalGap) : Infinity,
187
+ fixedMain: 0,
188
+ crossUsed: 0,
189
+ totalFlex: 0,
190
+ totalBasis: 0,
191
+ sizes: new Map(),
192
+ under: new Map(),
193
+ shrunkCap: new Map(),
194
+ sharedCap: new Map(),
195
+ shrunkBasis: new Map(),
180
196
  };
181
- // Pass 1: measure the fixed children (main extent + cross size) and total the flex.
182
- // A flex child's BASIS is reserved here too: it is main extent the leftover no longer contains,
183
- // exactly like a fixed child's size. With the default basis of 0 this line adds nothing.
184
- let fixedMain = 0;
185
- let totalFlex = 0;
186
- let totalBasis = 0;
187
- let crossUsed = 0;
188
- const fixedSize = new Map();
189
- for (const child of children) {
190
- if (child instanceof FlexiblePDFElement) {
191
- totalFlex += child.getFlex();
192
- totalBasis += child.getBasis(percentBase);
193
- }
194
- else {
195
- const size = child.calculateLayout(axis.measureConstraints(crossAvail, mainCapFor(child)), axis.offsetAt(mainStart, crossOrigin), ctx);
196
- fixedMain += axis.mainOf(size);
197
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
198
- fixedSize.set(child, size);
199
- }
200
- }
201
- // Shrinking: the line overflows and some children said they would give space back. CSS weights a
202
- // shrinker's share by `shrink x its own size`, so a big item yields more than a small one - which is
203
- // why this cannot reuse the grow maths above. Nobody shrinks by default, so a document that never
204
- // asks for it never enters this block.
205
- const shrinkers = children.filter((c) => c.flexShrink > 0 && !(c instanceof FlexiblePDFElement));
206
- const overflow = fixedMain + totalBasis + totalGap - mainAvail;
207
- if (shrinkers.length > 0 && Number.isFinite(mainAvail) && overflow > 0) {
208
- // Every child is floored at what it can actually hold - its `min-content` extent, which is CSS's
209
- // automatic `min-width` on a flex item. Without it a badly overflowing line would grind a
210
- // paragraph down to one word per line: the box gets narrow, the text gets tall, and nothing is
211
- // gained. Past that floor a child overflows again, which is what a browser does too.
212
- const naturalOf = (c) => axis.mainOf(fixedSize.get(c));
213
- const floorOf = new Map(shrinkers.map((c) => [
214
- c,
215
- // Never above its natural size: the shrink pass may only take space away, never hand it out.
216
- // Never below zero either - when the GAPS alone outgrow the line a proportional share asks
217
- // for more than a child has, and a negative constraint is nonsense even though
218
- // `constrainWidth` would floor it further down anyway.
219
- Math.min(naturalOf(c), Math.max(0, c.minIntrinsicMain(axis.mainHorizontal, ctx))),
220
- ]));
221
- // CSS resolves this iteratively (Flexbox 9.7): a child that lands on its floor is FROZEN there
222
- // and the share it could not absorb is handed to the rest. A single pass would leave that share
223
- // unspent, so one unsqueezable child would keep the whole line overflowing while its neighbours
224
- // still had room to give.
225
- const target = new Map(shrinkers.map((c) => [c, naturalOf(c)]));
226
- const frozen = new Set();
227
- let flexible = [...shrinkers];
228
- // What the frozen ones took off the overflow; the rest is always shared out from scratch, never
229
- // from what a previous round already assigned.
230
- let absorbedByFrozen = 0;
231
- while (flexible.length > 0) {
232
- const toAbsorb = overflow - absorbedByFrozen;
233
- if (toAbsorb <= 1e-9)
234
- break;
235
- const weight = (c) => c.flexShrink * naturalOf(c);
236
- const totalWeight = flexible.reduce((n, c) => n + weight(c), 0);
237
- if (totalWeight <= 0)
238
- break;
239
- const froze = [];
240
- for (const child of flexible) {
241
- const natural = naturalOf(child);
242
- const floor = floorOf.get(child);
243
- const wanted = natural - (weight(child) / totalWeight) * toAbsorb;
244
- if (wanted < floor) {
245
- target.set(child, floor);
246
- froze.push(child);
247
- absorbedByFrozen += natural - floor;
248
- }
249
- else {
250
- target.set(child, wanted);
251
- }
252
- }
253
- if (froze.length === 0)
254
- break;
255
- for (const c of froze)
256
- frozen.add(c);
257
- flexible = flexible.filter((c) => !frozen.has(c));
258
- }
259
- for (const child of shrinkers) {
260
- const natural = naturalOf(child);
261
- const capped = target.get(child);
262
- if (capped >= natural)
263
- continue;
264
- shrunkCap.set(child, capped);
265
- // Re-measure NOW, not in pass 2: a narrower child may wrap to more lines, and the line's
266
- // cross extent is settled just below.
267
- const size = child.calculateLayout(axis.measureConstraints(crossAvail, capped), axis.offsetAt(mainStart, crossOrigin), ctx);
268
- fixedMain += axis.mainOf(size) - natural;
269
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
270
- fixedSize.set(child, size);
271
- }
272
- }
273
- const leftover = mainAvail - fixedMain - totalBasis - totalGap;
197
+ measureLine(line);
198
+ shrinkLine(line);
199
+ const leftover = mainAvail - line.fixedMain - line.totalBasis - totalGap;
274
200
  // A flex child on an UNBOUNDED main axis has no leftover space to claim. It must collapse to zero,
275
201
  // never to `Infinity`: an infinite extent would become the offset of every following sibling and get
276
202
  // written into the content stream verbatim, silently corrupting the page from that point on.
277
203
  const remaining = Number.isFinite(leftover) ? Math.max(leftover, 0) : 0;
278
- // Main-axis distribution only kicks in with no flex child and bounded, positive space.
279
- let leadingSpace = 0;
280
- let betweenSpace = gap;
281
- if (totalFlex === 0 && mainAvail !== Infinity && leftover > 0) {
282
- if (main === "center")
283
- leadingSpace = leftover / 2;
284
- else if (main === "end")
285
- leadingSpace = leftover;
286
- else if (main === "between" && count > 1)
287
- betweenSpace = gap + leftover / (count - 1);
288
- else if (main === "around") {
289
- const unit = leftover / count;
290
- leadingSpace = unit / 2;
291
- betweenSpace = gap + unit;
292
- }
293
- }
294
- // Measure flex children too (at their main share) so a tall/wrapping flex cell counts
295
- // toward the line's cross extent - they're placed in pass 2, but crossExtent needs them now.
296
- if (totalFlex > 0) {
297
- for (const child of children) {
298
- if (child instanceof FlexiblePDFElement) {
299
- const mainExtent = child.getBasis(percentBase) + (child.getFlex() / totalFlex) * remaining;
300
- const size = child.calculateLayout(axis.flexConstraints(mainExtent, crossAvail), axis.offsetAt(mainStart, crossOrigin), ctx);
301
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
302
- }
303
- }
304
- }
204
+ const weight = shareLeftover(line, remaining);
205
+ const spacing = mainSpacing(mainAlignOf(options), gap, ordered.length, mainAvail, leftover, weight);
206
+ measureFlex(line, weight, remaining);
305
207
  // The cross extent children align within: the bounded line size, else the tallest child.
306
- const crossExtent = crossAvail !== Infinity ? crossAvail : crossUsed;
307
- // `stretch` caps a child's cross to `crossExtent` (not `crossAvail`) so siblings end up
308
- // equal across the axis. Bounded lines have crossExtent == crossAvail (byte-identical);
309
- // only an unbounded line (a shrink-wrap Row) now equalises instead of staying natural.
310
- // A child may override the container with its own `alignSelf` (CSS), so the decision is per child.
311
- const alignFor = (child) => child.alignSelf ?? cross;
312
- // Pass 2: place each child at the running main position, offset across by its alignment.
313
- let mainPos = mainStart + leadingSpace;
314
- let placedCross = 0;
315
- children.forEach((child, index) => {
316
- const align = alignFor(child);
317
- const stretch = align === "stretch";
318
- let mainExtent;
319
- if (child instanceof FlexiblePDFElement) {
320
- // `totalFlex` is 0 when every flex child asked for `flex: 0` (a pure `flexBasis` slot). The
321
- // share is then 0/0 - a NaN that becomes the offset of every later sibling, which is the
322
- // shape of the old Spacer bug. Such a child simply takes its basis.
323
- const share = totalFlex > 0 ? (child.getFlex() / totalFlex) * remaining : 0;
324
- mainExtent = child.getBasis(percentBase) + share;
325
- // A flex child fills the MAIN axis, and its cross size is only known after layout - there is
326
- // nothing to align against. So alignSelf is a no-op here, and that has to hold for the CROSS
327
- // CONSTRAINT too: reading the per-child alignment would hand an `alignSelf: "start"` flex child
328
- // an unbounded cross axis on a shrink-wrapping line, where the container's own rule gives it
329
- // the line's extent. A no-op that changes the constraints is not a no-op.
330
- const size = child.calculateLayout(axis.flexConstraints(mainExtent, cross === "stretch" ? crossExtent : crossAvail), axis.offsetAt(mainPos, crossOrigin), ctx);
331
- placedCross = Math.max(placedCross, axis.crossOf(size));
208
+ const crossExtent = crossAvail !== Infinity && !crossFromContent ? crossAvail : line.crossUsed;
209
+ return placeLine(line, options.cross ?? "stretch", crossExtent, crossOrigin, spacing, weight, remaining, mode);
210
+ }
211
+ }
212
+ /** A flex child's basis on this line: what it asked for, or less once the line has shrunk it. */
213
+ function basisOf(line, child, flexible) {
214
+ return line.shrunkBasis.get(child) ?? flexible.getBasis(line.percentBase);
215
+ }
216
+ /**
217
+ * The main cap to hand a child: its shrunk or shared cap once the line is settled; `percentBase` for a
218
+ * percentage child, so its fraction resolves; unbounded for everyone else, who keep their natural size.
219
+ */
220
+ function mainCapFor(line, child) {
221
+ const target = line.shrunkCap.get(child) ?? line.sharedCap.get(child);
222
+ if (target !== undefined)
223
+ return target;
224
+ if (line.percentBase === Infinity)
225
+ return Infinity;
226
+ return child.relativeSizeFactor(line.axis.mainHorizontal) !== undefined
227
+ ? line.percentBase
228
+ : Infinity;
229
+ }
230
+ /** Measures `child` and remembers the size with the constraints it answers. */
231
+ function measureInto(line, child, constraints) {
232
+ const size = child.measure(constraints, line.ctx);
233
+ line.sizes.set(child, size);
234
+ line.under.set(child, constraints);
235
+ return size;
236
+ }
237
+ /** Measures a fixed child again at a new main cap, moving the line's totals with it. */
238
+ function remeasure(line, child, cap) {
239
+ const before = line.axis.mainOf(line.sizes.get(child));
240
+ const size = measureInto(line, child, line.axis.measureConstraints(line.crossAvail, cap));
241
+ line.fixedMain += line.axis.mainOf(size) - before;
242
+ line.crossUsed = Math.max(line.crossUsed, line.axis.crossOf(size));
243
+ }
244
+ /**
245
+ * Step 1: measure the fixed children (main extent + cross size) and total the flex. A flex child's BASIS
246
+ * is reserved here too: it is main extent the leftover no longer contains, exactly like a fixed child's
247
+ * size. With the default basis of 0 it adds nothing.
248
+ */
249
+ function measureLine(line) {
250
+ const { axis } = line;
251
+ for (const child of line.children) {
252
+ const flexible = flexibleOf(child);
253
+ if (flexible) {
254
+ line.totalFlex += flexible.getFlex();
255
+ line.totalBasis += flexible.getBasis(line.percentBase);
256
+ continue;
257
+ }
258
+ const size = measureInto(line, child, axis.measureConstraints(line.crossAvail, mainCapFor(line, child)));
259
+ line.fixedMain += axis.mainOf(size);
260
+ line.crossUsed = Math.max(line.crossUsed, axis.crossOf(size));
261
+ }
262
+ }
263
+ /**
264
+ * Step 2: the line overflows and some children said they would give space back. CSS weights a shrinker's
265
+ * share by `shrink x its own size`, so a big item yields more than a small one - for a flex child that
266
+ * size is its basis, so a Spacer (basis 0) gives nothing. A document that never overflows never gets past
267
+ * the first test.
268
+ */
269
+ function shrinkLine(line) {
270
+ const { axis } = line;
271
+ const overflow = line.fixedMain + line.totalBasis + line.totalGap - line.mainAvail;
272
+ if (!Number.isFinite(line.mainAvail) || !(overflow > 0))
273
+ return;
274
+ const shrinkers = line.children.filter((c) => {
275
+ if (!(flexShrinkOf(c) > 0))
276
+ return false;
277
+ const flexible = flexibleOf(c);
278
+ return !flexible || flexible.getBasis(line.percentBase) > 0;
279
+ });
280
+ if (shrinkers.length === 0)
281
+ return;
282
+ // Every child is floored at what it can actually hold - its `min-content` extent, which is CSS's
283
+ // automatic `min-width` on a flex item. Without it a badly overflowing line would grind a paragraph
284
+ // down to one word per line. Past that floor a child overflows again, as in a browser.
285
+ const naturalOf = (c) => {
286
+ const flexible = flexibleOf(c);
287
+ return flexible ? flexible.getBasis(line.percentBase) : axis.mainOf(line.sizes.get(c));
288
+ };
289
+ const floorOf = new Map(shrinkers.map((c) => [
290
+ c,
291
+ // Never above its natural size (the shrink only takes space away) and never below zero (when the
292
+ // GAPS alone outgrow the line, a proportional share asks for more than a child has).
293
+ Math.min(naturalOf(c), Math.max(0, c.minIntrinsicMain(axis.mainHorizontal, line.ctx))),
294
+ ]));
295
+ // CSS resolves this iteratively (Flexbox 9.7): a child that lands on its floor is FROZEN there and the
296
+ // share it could not absorb is handed to the rest. A single pass would leave that share unspent.
297
+ const target = new Map(shrinkers.map((c) => [c, naturalOf(c)]));
298
+ const frozen = new Set();
299
+ let flexible = [...shrinkers];
300
+ // What the frozen ones took off the overflow; the rest is always shared out from scratch.
301
+ let absorbedByFrozen = 0;
302
+ while (flexible.length > 0) {
303
+ const toAbsorb = overflow - absorbedByFrozen;
304
+ if (toAbsorb <= 1e-9)
305
+ break;
306
+ const weight = (c) => flexShrinkOf(c) * naturalOf(c);
307
+ const totalWeight = flexible.reduce((n, c) => n + weight(c), 0);
308
+ if (totalWeight <= 0)
309
+ break;
310
+ const froze = [];
311
+ for (const child of flexible) {
312
+ const natural = naturalOf(child);
313
+ const floor = floorOf.get(child);
314
+ const wanted = natural - (weight(child) / totalWeight) * toAbsorb;
315
+ if (wanted < floor) {
316
+ target.set(child, floor);
317
+ froze.push(child);
318
+ absorbedByFrozen += natural - floor;
332
319
  }
333
320
  else {
334
- const childCross = axis.crossOf(fixedSize.get(child));
335
- const size = child.calculateLayout(axis.measureConstraints(stretch ? crossExtent : crossAvail, mainCapFor(child)), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, childCross)), ctx);
336
- mainExtent = axis.mainOf(size);
337
- placedCross = Math.max(placedCross, axis.crossOf(size));
321
+ target.set(child, wanted);
338
322
  }
339
- mainPos += mainExtent;
340
- if (index < count - 1)
341
- mainPos += betweenSpace;
342
- });
343
- return { mainUsed: mainPos - mainStart, crossUsed: placedCross };
323
+ }
324
+ if (froze.length === 0)
325
+ break;
326
+ for (const c of froze)
327
+ frozen.add(c);
328
+ flexible = flexible.filter((c) => !frozen.has(c));
329
+ }
330
+ for (const child of shrinkers) {
331
+ const capped = target.get(child);
332
+ if (capped >= naturalOf(child))
333
+ continue;
334
+ if (flexibleOf(child)) {
335
+ // A flex child is measured later, at its basis plus its share; it starts from less.
336
+ line.totalBasis -= naturalOf(child) - capped;
337
+ line.shrunkBasis.set(child, capped);
338
+ continue;
339
+ }
340
+ line.shrunkCap.set(child, capped);
341
+ // Re-measured NOW: a narrower child may wrap to more lines, and the line's cross extent is
342
+ // settled before anything is placed.
343
+ remeasure(line, child, capped);
344
+ }
345
+ }
346
+ /**
347
+ * Step 3: a stack holding a Spacer grows like an `Expanded` of flex 1 whose basis is its content - it
348
+ * shares what is left with the flex children. Handing it the whole line squeezed its siblings; handing
349
+ * it a share before the line was settled could leave it less than its own content. Returns the total
350
+ * weight the leftover is shared by.
351
+ */
352
+ function shareLeftover(line, remaining) {
353
+ const { axis } = line;
354
+ const sharing = line.children.filter((child) => line.percentBase !== Infinity &&
355
+ !flexibleOf(child) &&
356
+ child.relativeSizeFactor(axis.mainHorizontal) === undefined &&
357
+ child.needsBoundedMain(axis.mainHorizontal));
358
+ const weight = line.totalFlex + sharing.length;
359
+ if (remaining > 0) {
360
+ for (const child of sharing) {
361
+ const grown = axis.mainOf(line.sizes.get(child)) + remaining / weight;
362
+ line.sharedCap.set(child, grown);
363
+ remeasure(line, child, grown);
364
+ }
365
+ }
366
+ return weight;
367
+ }
368
+ /** Step 4: the main-axis distribution - only with nothing to flex and bounded, positive space. */
369
+ function mainSpacing(main, gap, count, mainAvail, leftover, weight) {
370
+ if (weight !== 0 || mainAvail === Infinity || !(leftover > 0))
371
+ return { leading: 0, between: gap };
372
+ if (main === "center")
373
+ return { leading: leftover / 2, between: gap };
374
+ if (main === "end")
375
+ return { leading: leftover, between: gap };
376
+ if (main === "between" && count > 1)
377
+ return { leading: 0, between: gap + leftover / (count - 1) };
378
+ if (main === "around") {
379
+ const unit = leftover / count;
380
+ return { leading: unit / 2, between: gap + unit };
381
+ }
382
+ return { leading: 0, between: gap };
383
+ }
384
+ /**
385
+ * Step 5: measure the flex children at their share, so a tall or wrapping flex cell counts toward the
386
+ * line's cross extent before anything is placed.
387
+ */
388
+ function measureFlex(line, weight, remaining) {
389
+ for (const child of line.children) {
390
+ const flexible = flexibleOf(child);
391
+ if (!flexible)
392
+ continue;
393
+ // A pure `flexBasis` slot (`flex: 0`) has no share but still has a size across the line.
394
+ const share = weight > 0 ? (flexible.getFlex() / weight) * remaining : 0;
395
+ const mainExtent = basisOf(line, child, flexible) + share;
396
+ const size = measureInto(line, child, line.axis.flexConstraints(mainExtent, line.crossAvail));
397
+ line.crossUsed = Math.max(line.crossUsed, line.axis.crossOf(size));
344
398
  }
345
399
  }
400
+ /**
401
+ * Step 6: place each child at the running main position, offset across by its alignment. This is the
402
+ * ONE layout a child gets. Measuring, it only sizes - and reuses a size already measured under the same
403
+ * constraints, or the measuring itself would double at every level.
404
+ *
405
+ * `stretch` caps a child's cross to `crossExtent`, not `crossAvail`, so siblings end up equal across the
406
+ * axis. A child may override the container with its own `alignSelf` (CSS).
407
+ */
408
+ function placeLine(line, cross, crossExtent, crossOrigin, spacing, weight, remaining, mode) {
409
+ const { axis, ctx } = line;
410
+ const place = (child, constraints, offset) => {
411
+ if (mode === "layout")
412
+ return child.calculateLayout(constraints, offset, ctx);
413
+ const known = line.under.get(child);
414
+ return known && sameConstraints(known, constraints)
415
+ ? line.sizes.get(child)
416
+ : child.measure(constraints, ctx);
417
+ };
418
+ const last = line.children.length - 1;
419
+ let mainPos = spacing.leading;
420
+ let placedCross = 0;
421
+ line.children.forEach((child, index) => {
422
+ let mainExtent;
423
+ const flexible = flexibleOf(child);
424
+ if (flexible) {
425
+ // `totalFlex` is 0 when every flex child asked for `flex: 0` (a pure `flexBasis` slot). The share
426
+ // is then 0/0 - a NaN that becomes the offset of every later sibling. Such a child takes its basis.
427
+ const share = weight > 0 ? (flexible.getFlex() / weight) * remaining : 0;
428
+ mainExtent = basisOf(line, child, flexible) + share;
429
+ // A flex child fills the MAIN axis and its cross size is only known after layout, so `alignSelf`
430
+ // is a no-op here - and that has to hold for the CROSS CONSTRAINT too, or an `alignSelf: "start"`
431
+ // flex child would get an unbounded cross axis on a shrink-wrapping line.
432
+ const size = place(child, axis.flexConstraints(mainExtent, cross === "stretch" ? crossExtent : line.crossAvail), axis.offsetAt(mainPos, crossOrigin));
433
+ placedCross = Math.max(placedCross, axis.crossOf(size));
434
+ }
435
+ else {
436
+ const align = alignSelfOf(child) ?? cross;
437
+ const childCross = axis.crossOf(line.sizes.get(child));
438
+ const size = place(child, axis.measureConstraints(align === "stretch" ? crossExtent : line.crossAvail, mainCapFor(line, child)), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, childCross)));
439
+ mainExtent = axis.mainOf(size);
440
+ placedCross = Math.max(placedCross, axis.crossOf(size));
441
+ }
442
+ mainPos += mainExtent;
443
+ if (index < last)
444
+ mainPos += spacing.between;
445
+ });
446
+ return { mainUsed: mainPos, crossUsed: placedCross };
447
+ }
@@ -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;
@@ -9,7 +9,8 @@ DataView.prototype.getUint24 = function (byteOffset, littleEndian = false) {
9
9
  return littleEndian ? (b3 << 16) | (b2 << 8) | b1 : (b1 << 16) | (b2 << 8) | b3;
10
10
  };
11
11
  export async function getImageDimensions(buffer) {
12
- const dataView = new DataView(buffer.buffer);
12
+ // The view's own window: a `Uint8Array` may be a slice of a larger buffer (a Node `Buffer` usually is).
13
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
13
14
  // Check for JPEG (0xFFD8 is the start of JPEG file)
14
15
  if (dataView.getUint16(0) === 0xffd8) {
15
16
  let offset = 2;