@jasy/pdf 1.0.0-beta.4 → 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 (109) 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 +7 -2
  4. package/dist/api/content.js +17 -7
  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/gradient.d.ts +1 -53
  9. package/dist/api/gradient.js +2 -97
  10. package/dist/api/index.d.ts +2 -0
  11. package/dist/api/index.js +2 -0
  12. package/dist/api/layout.js +4 -3
  13. package/dist/api/table.js +3 -1
  14. package/dist/api/text.d.ts +8 -8
  15. package/dist/api/text.js +6 -0
  16. package/dist/canvas/painter.d.ts +2 -2
  17. package/dist/canvas/painter.js +2 -2
  18. package/dist/common/color-input.d.ts +24 -0
  19. package/dist/common/color-input.js +278 -0
  20. package/dist/common/diagnostics.d.ts +9 -1
  21. package/dist/common/gradient.d.ts +53 -0
  22. package/dist/common/gradient.js +97 -0
  23. package/dist/elements/container-element.d.ts +3 -0
  24. package/dist/elements/container-element.js +17 -5
  25. package/dist/elements/image-element.d.ts +37 -26
  26. package/dist/elements/image-element.js +88 -50
  27. package/dist/elements/index.d.ts +3 -0
  28. package/dist/elements/index.js +3 -0
  29. package/dist/elements/layout/column-break-element.d.ts +17 -0
  30. package/dist/elements/layout/column-break-element.js +26 -0
  31. package/dist/elements/layout/deferred-element.d.ts +9 -1
  32. package/dist/elements/layout/deferred-element.js +18 -3
  33. package/dist/elements/layout/expanded-element.d.ts +4 -0
  34. package/dist/elements/layout/expanded-element.js +14 -1
  35. package/dist/elements/layout/flow-element.d.ts +109 -0
  36. package/dist/elements/layout/flow-element.js +317 -0
  37. package/dist/elements/layout/keep-together-element.js +14 -2
  38. package/dist/elements/layout/padding-element.d.ts +3 -0
  39. package/dist/elements/layout/padding-element.js +9 -2
  40. package/dist/elements/layout/page-builder-element.js +2 -2
  41. package/dist/elements/layout/positioned-element.d.ts +9 -2
  42. package/dist/elements/layout/positioned-element.js +6 -2
  43. package/dist/elements/layout/proxy-element.d.ts +4 -0
  44. package/dist/elements/layout/proxy-element.js +12 -0
  45. package/dist/elements/layout/region-element.d.ts +53 -0
  46. package/dist/elements/layout/region-element.js +108 -0
  47. package/dist/elements/layout/repeating-header-element.d.ts +3 -0
  48. package/dist/elements/layout/repeating-header-element.js +16 -1
  49. package/dist/elements/layout/rotated-element.d.ts +2 -0
  50. package/dist/elements/layout/rotated-element.js +8 -0
  51. package/dist/elements/pdf-element.d.ts +32 -3
  52. package/dist/elements/pdf-element.js +50 -1
  53. package/dist/elements/rectangle-element.d.ts +12 -6
  54. package/dist/elements/rectangle-element.js +31 -12
  55. package/dist/elements/row-element.d.ts +6 -12
  56. package/dist/elements/row-element.js +34 -4
  57. package/dist/elements/svg-element.js +4 -10
  58. package/dist/elements/text-element.d.ts +17 -1
  59. package/dist/elements/text-element.js +63 -40
  60. package/dist/ir/display-list.d.ts +6 -1
  61. package/dist/layout/box-constraints.d.ts +12 -0
  62. package/dist/layout/box-constraints.js +17 -0
  63. package/dist/layout/collect-images.js +8 -4
  64. package/dist/layout/flex-item.d.ts +2 -0
  65. package/dist/layout/flex-item.js +14 -2
  66. package/dist/layout/fragmentation.d.ts +11 -5
  67. package/dist/layout/fragmentation.js +27 -7
  68. package/dist/layout/layout-cache.d.ts +8 -8
  69. package/dist/layout/layout-cache.js +10 -7
  70. package/dist/platform/browser-fs.d.ts +1 -2
  71. package/dist/platform/browser-fs.js +3 -6
  72. package/dist/platform/node-fs.d.ts +0 -1
  73. package/dist/platform/node-fs.js +0 -4
  74. package/dist/renderer/column-break-renderer.d.ts +8 -0
  75. package/dist/renderer/column-break-renderer.js +15 -0
  76. package/dist/renderer/emoji-image.js +3 -1
  77. package/dist/renderer/flow-renderer.d.ts +16 -0
  78. package/dist/renderer/flow-renderer.js +21 -0
  79. package/dist/renderer/image-renderer.js +31 -54
  80. package/dist/renderer/index.d.ts +2 -0
  81. package/dist/renderer/index.js +2 -0
  82. package/dist/renderer/page-renderer.js +3 -4
  83. package/dist/renderer/pdf-backend.js +9 -2
  84. package/dist/renderer/pdf-document-renderer.js +2 -1
  85. package/dist/renderer/pdf-renderer.js +9 -0
  86. package/dist/renderer/positioned-renderer.d.ts +14 -2
  87. package/dist/renderer/positioned-renderer.js +40 -4
  88. package/dist/renderer/rectangle-renderer.d.ts +1 -1
  89. package/dist/renderer/rectangle-renderer.js +11 -6
  90. package/dist/renderer/rotated-renderer.js +3 -2
  91. package/dist/renderer/text-renderer.js +25 -28
  92. package/dist/svg/index.d.ts +1 -1
  93. package/dist/svg/index.js +26 -5
  94. package/dist/svg/style.js +1 -1
  95. package/dist/text/line-breaker.d.ts +23 -0
  96. package/dist/text/line-breaker.js +142 -65
  97. package/dist/text/line-metrics.d.ts +2 -0
  98. package/dist/text/line-metrics.js +5 -1
  99. package/dist/text/word-splitting.js +11 -5
  100. package/dist/utils/flex-layout.d.ts +3 -1
  101. package/dist/utils/flex-layout.js +141 -33
  102. package/dist/utils/image-helper.d.ts +59 -11
  103. package/dist/utils/image-helper.js +207 -80
  104. package/dist/utils/pdf-object-manager.d.ts +6 -3
  105. package/dist/utils/pdf-object-manager.js +31 -7
  106. package/dist/utils/renderer-registry.d.ts +6 -0
  107. package/dist/utils/renderer-registry.js +1 -1
  108. package/dist/utils/ttf-parser.js +3 -2
  109. package/package.json +1 -1
@@ -29,8 +29,8 @@ export function splitLongWord(word, room, font, metrics, letterSpacing, options)
29
29
  // the blunt one - which is what an e-mail address or an IBAN needs, having no valid point at all.
30
30
  const head = (hyphenate ? longestHyphenatedPrefix(rest, room, width, hyphenate) : null) ??
31
31
  (breakWord ? longestPrefix(rest, room, width) : null);
32
- // Nothing fits - not even one character, or hyphenation found nothing and break-word is off.
33
- // Leave the remainder whole and let it overflow, exactly as before.
32
+ // Hyphenation found nothing and break-word is off, or one character is all that is left: leave the
33
+ // remainder whole and let it overflow.
34
34
  if (head === null)
35
35
  break;
36
36
  // A hyphenator that returns text other than the word is not splitting it: leave it whole.
@@ -90,9 +90,15 @@ function longestPrefix(word, room, width) {
90
90
  taken += char;
91
91
  count += 1;
92
92
  }
93
- // Not even one character fits: the box is narrower than a single glyph. Splitting cannot help, and
94
- // looping on it would never terminate.
95
- if (count === 0 || count === chars.length)
93
+ if (count === chars.length)
96
94
  return null;
95
+ // Not even one character fits - the box is narrower than a glyph: one character per line, as CSS
96
+ // `overflow-wrap` sets it. Each pass still takes one, so the loop ends.
97
+ if (count === 0) {
98
+ if (chars.length === 1)
99
+ return null;
100
+ taken = chars[0];
101
+ count = 1;
102
+ }
97
103
  return { text: taken, taken, rest: chars.slice(count).join(""), hyphen: false };
98
104
  }
@@ -29,7 +29,8 @@ export interface FlexAxis {
29
29
  * unbounded main axis and shrink-wraps as before.
30
30
  */
31
31
  measureConstraints(crossAvail: number, mainMax?: number): BoxConstraints;
32
- /** Constraints for a flex child (fills the cross axis like a stretched child). */
32
+ /** Constraints for a flex child: its main extent is SET (it fills its share, whatever size it asked
33
+ * for), across it is an ordinary child. */
33
34
  flexConstraints(mainExtent: number, crossAvail: number): BoxConstraints;
34
35
  /** Offset (from the stack's top-left) of a child at main position `mainPos`, cross `crossPos`. */
35
36
  offsetAt(mainPos: number, crossPos: number): Offset;
@@ -58,6 +59,7 @@ export declare class FlexLayoutHelper {
58
59
  static layout(children: PDFElement[], axis: FlexAxis, mainAvail: number, crossAvail: number, options: FlexOptions, ctx: LayoutContext, mode?: FlexMode): {
59
60
  mainUsed: number;
60
61
  crossUsed: number;
62
+ widestLine?: number;
61
63
  };
62
64
  /**
63
65
  * The wrapping path: split into lines, lay each one out, then place the lines across the axis.
@@ -9,7 +9,7 @@ export const VERTICAL_AXIS = {
9
9
  mainOf: (s) => s.height,
10
10
  crossOf: (s) => s.width,
11
11
  measureConstraints: (crossAvail, mainMax = Infinity) => BoxConstraints.loose(crossAvail, mainMax),
12
- flexConstraints: (mainExtent, crossAvail) => BoxConstraints.loose(crossAvail, mainExtent),
12
+ flexConstraints: (mainExtent, crossAvail) => new BoxConstraints(0, crossAvail, mainExtent, mainExtent),
13
13
  offsetAt: (mainPos, crossPos) => ({ x: crossPos, y: mainPos }),
14
14
  };
15
15
  export const HORIZONTAL_AXIS = {
@@ -17,7 +17,7 @@ export const HORIZONTAL_AXIS = {
17
17
  mainOf: (s) => s.width,
18
18
  crossOf: (s) => s.height,
19
19
  measureConstraints: (crossAvail, mainMax = Infinity) => BoxConstraints.loose(mainMax, crossAvail),
20
- flexConstraints: (mainExtent, crossAvail) => BoxConstraints.loose(mainExtent, crossAvail),
20
+ flexConstraints: (mainExtent, crossAvail) => new BoxConstraints(mainExtent, mainExtent, 0, crossAvail),
21
21
  offsetAt: (mainPos, crossPos) => ({ x: mainPos, y: crossPos }),
22
22
  };
23
23
  /** Cross-axis offset of a child of size `childCross` within `crossExtent`. */
@@ -123,12 +123,24 @@ export class FlexLayoutHelper {
123
123
  // Each line keeps SOURCE order internally: the ordering was applied above, and applying it again
124
124
  // per line would reverse twice.
125
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));
131
- const heights = measure.map((m) => m.crossUsed);
126
+ const measureAt = (line, cross) => FlexLayoutHelper.layoutLine(line, axis, mainAvail, cross, 0, lineOptions, ctx, "measure", true);
127
+ // Across a Column a child gets the column's width, as a block does in CSS (a paragraph wraps inside
128
+ // it); across a Row it is as tall as its content. Either way the line is only as big as what is on
129
+ // it, so `stretch` fills the line and not the container. A region-sized child (a Flow) has no
130
+ // content height: its line shares what the other lines leave of the Row's height.
131
+ const regionLine = (line) => axis.mainHorizontal && line.some((c) => c.needsRegion());
132
+ const measure = lines.map((line) => regionLine(line) ? null : measureAt(line, axis.mainHorizontal ? Infinity : crossAvail));
133
+ const regionLines = lines.filter(regionLine).length;
134
+ if (regionLines > 0) {
135
+ const taken = measure.reduce((n, m) => n + (m?.crossUsed ?? 0), 0) + Math.max(0, lines.length - 1) * gap;
136
+ const share = Math.max(0, crossAvail - taken) / regionLines;
137
+ lines.forEach((line, i) => {
138
+ if (regionLine(line))
139
+ measure[i] = measureAt(line, share);
140
+ });
141
+ }
142
+ const sized = measure;
143
+ const heights = sized.map((m) => m.crossUsed);
132
144
  const totalCross = heights.reduce((n, h) => n + h, 0) + Math.max(0, lines.length - 1) * gap;
133
145
  // `alignContent` distributes the BLOCK of lines across the axis, using the same vocabulary the main
134
146
  // axis uses. Only meaningful in a bounded cross axis with room left over.
@@ -156,7 +168,12 @@ export class FlexLayoutHelper {
156
168
  if (i < lines.length - 1)
157
169
  cursor += between;
158
170
  });
159
- return { mainUsed: mainAvail, crossUsed: Math.max(totalCross, cursor) };
171
+ return {
172
+ mainUsed: mainAvail,
173
+ crossUsed: Math.max(totalCross, cursor),
174
+ // A child wider than the line runs past it, in a line of its own.
175
+ widestLine: Math.max(...sized.map((m) => m.mainUsed)),
176
+ };
160
177
  }
161
178
  /**
162
179
  * Lays out ONE flex line along `axis` and places every child. Fixed children take their natural main
@@ -193,6 +210,9 @@ export class FlexLayoutHelper {
193
210
  shrunkCap: new Map(),
194
211
  sharedCap: new Map(),
195
212
  shrunkBasis: new Map(),
213
+ flexFloor: new Map(),
214
+ flexCeil: new Map(),
215
+ flexExtent: new Map(),
196
216
  };
197
217
  measureLine(line);
198
218
  shrinkLine(line);
@@ -203,10 +223,11 @@ export class FlexLayoutHelper {
203
223
  const remaining = Number.isFinite(leftover) ? Math.max(leftover, 0) : 0;
204
224
  const weight = shareLeftover(line, remaining);
205
225
  const spacing = mainSpacing(mainAlignOf(options), gap, ordered.length, mainAvail, leftover, weight);
206
- measureFlex(line, weight, remaining);
226
+ shareFlex(line, weight, remaining);
227
+ measureFlex(line, options.cross ?? "stretch");
207
228
  // The cross extent children align within: the bounded line size, else the tallest child.
208
229
  const crossExtent = crossAvail !== Infinity && !crossFromContent ? crossAvail : line.crossUsed;
209
- return placeLine(line, options.cross ?? "stretch", crossExtent, crossOrigin, spacing, weight, remaining, mode);
230
+ return placeLine(line, options.cross ?? "stretch", crossExtent, crossOrigin, spacing, mode);
210
231
  }
211
232
  }
212
233
  /** A flex child's basis on this line: what it asked for, or less once the line has shrunk it. */
@@ -253,6 +274,17 @@ function measureLine(line) {
253
274
  if (flexible) {
254
275
  line.totalFlex += flexible.getFlex();
255
276
  line.totalBasis += flexible.getBasis(line.percentBase);
277
+ if (Number.isFinite(line.mainAvail)) {
278
+ // Down a column, a flex child keeps its content's height (CSS `min-height: auto`): squeezed
279
+ // below it, it would draw over the sibling that follows.
280
+ const content = axis.mainHorizontal
281
+ ? 0
282
+ : axis.mainOf(child.measure(axis.measureConstraints(line.crossAvail, Infinity), line.ctx));
283
+ const floor = Math.max(0, child.minIntrinsicMain(axis.mainHorizontal, line.ctx), content);
284
+ line.flexFloor.set(child, floor);
285
+ // A max below the floor loses, as in CSS.
286
+ line.flexCeil.set(child, Math.max(floor, child.maxExtent(axis.mainHorizontal)));
287
+ }
256
288
  continue;
257
289
  }
258
290
  const size = measureInto(line, child, axis.measureConstraints(line.crossAvail, mainCapFor(line, child)));
@@ -263,12 +295,17 @@ function measureLine(line) {
263
295
  /**
264
296
  * Step 2: the line overflows and some children said they would give space back. CSS weights a shrinker's
265
297
  * 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.
298
+ * size is its basis, so a Spacer (basis 0) gives nothing. A flex child counts at least at its floor, so
299
+ * its siblings make room for what it holds. A document that never overflows never gets past the first
300
+ * test.
268
301
  */
269
302
  function shrinkLine(line) {
270
303
  const { axis } = line;
271
- const overflow = line.fixedMain + line.totalBasis + line.totalGap - line.mainAvail;
304
+ let reserved = 0;
305
+ for (const [child, floor] of line.flexFloor) {
306
+ reserved += Math.max(0, floor - flexibleOf(child).getBasis(line.percentBase));
307
+ }
308
+ const overflow = line.fixedMain + line.totalBasis + reserved + line.totalGap - line.mainAvail;
272
309
  if (!Number.isFinite(line.mainAvail) || !(overflow > 0))
273
310
  return;
274
311
  const shrinkers = line.children.filter((c) => {
@@ -382,30 +419,100 @@ function mainSpacing(main, gap, count, mainAvail, leftover, weight) {
382
419
  return { leading: 0, between: gap };
383
420
  }
384
421
  /**
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.
422
+ * Step 5: each flex child's main extent - its basis plus its share of the leftover, held between its
423
+ * floor and its ceiling. CSS resolves that iteratively (Flexbox 9.7): clamp everyone, and by the sign of
424
+ * the total change freeze the ones held at their floor (it grew) or at their ceiling (it shrank); the
425
+ * rest share what is left again. Nothing clamped: the plain share.
387
426
  */
388
- function measureFlex(line, weight, remaining) {
427
+ function shareFlex(line, weight, remaining) {
428
+ const items = [];
389
429
  for (const child of line.children) {
390
430
  const flexible = flexibleOf(child);
391
- if (!flexible)
392
- continue;
431
+ if (flexible)
432
+ items.push([child, flexible]);
433
+ }
434
+ // `totalFlex` is 0 when every flex child asked for `flex: 0` (a pure `flexBasis` slot). The share is
435
+ // then 0/0 - a NaN that becomes the offset of every later sibling. Such a child takes its basis.
436
+ const extentAt = (child, flexible, part, of) => basisOf(line, child, flexible) + (of > 0 ? (flexible.getFlex() / of) * part : 0);
437
+ for (const [child, flexible] of items) {
438
+ line.flexExtent.set(child, extentAt(child, flexible, remaining, weight));
439
+ }
440
+ // The flex children's part of the leftover; a Spacer stack's share (`shareLeftover`) stays its own.
441
+ let pot = weight > 0 ? (remaining * line.totalFlex) / weight : 0;
442
+ let active = items;
443
+ for (;;) {
444
+ let change = 0;
445
+ const clamped = new Map();
446
+ for (const [child] of active) {
447
+ const extent = line.flexExtent.get(child);
448
+ const held = Math.min(Math.max(extent, line.flexFloor.get(child) ?? 0), line.flexCeil.get(child) ?? Infinity);
449
+ if (held !== extent)
450
+ clamped.set(child, held);
451
+ change += held - extent;
452
+ }
453
+ if (clamped.size === 0)
454
+ return;
455
+ const froze = active.filter(([child]) => {
456
+ const held = clamped.get(child);
457
+ return (held !== undefined &&
458
+ (change > 0
459
+ ? held > line.flexExtent.get(child)
460
+ : change < 0
461
+ ? held < line.flexExtent.get(child)
462
+ : true));
463
+ });
464
+ for (const [child, flexible] of froze) {
465
+ const held = clamped.get(child);
466
+ pot -= held - basisOf(line, child, flexible);
467
+ line.flexExtent.set(child, held);
468
+ }
469
+ active = active.filter((item) => !froze.includes(item));
470
+ const activeFlex = active.reduce((n, [, flexible]) => n + flexible.getFlex(), 0);
471
+ for (const [child, flexible] of active) {
472
+ line.flexExtent.set(child, extentAt(child, flexible, Math.max(0, pot), activeFlex));
473
+ }
474
+ }
475
+ }
476
+ /**
477
+ * Step 6: measure the flex children at their extent, so a tall or wrapping flex cell counts toward the
478
+ * line's cross extent before anything is placed.
479
+ */
480
+ function measureFlex(line, cross) {
481
+ for (const [child, mainExtent] of line.flexExtent) {
393
482
  // 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));
483
+ const crossMax = (alignSelfOf(child) ?? cross) === "stretch"
484
+ ? line.crossAvail
485
+ : ownCross(line, child, mainExtent);
486
+ const size = measureInto(line, child, line.axis.flexConstraints(mainExtent, crossMax));
397
487
  line.crossUsed = Math.max(line.crossUsed, line.axis.crossOf(size));
398
488
  }
399
489
  }
400
490
  /**
401
- * Step 6: place each child at the running main position, offset across by its alignment. This is the
491
+ * Across the line, a flex child that is not stretched takes what its content needs and is aligned like
492
+ * any other item. Its content is measured against the line's cross bound - an unbounded one would stop a
493
+ * text in a Column from wrapping.
494
+ */
495
+ function ownCross(line, child, mainExtent) {
496
+ const flexible = flexibleOf(child);
497
+ const content = flexible.layoutChildren()[0];
498
+ if (!content)
499
+ return 0;
500
+ // In the context it is laid out in: a wrapper on the way to the `Expanded` may hand one down.
501
+ let ctx = line.ctx;
502
+ for (let el = child; el !== flexible; el = el.proxied())
503
+ ctx = el.proxiedContext(ctx);
504
+ const constraints = line.axis.flexConstraints(mainExtent, line.crossAvail);
505
+ return line.axis.crossOf(content.measure(constraints, ctx));
506
+ }
507
+ /**
508
+ * Step 7: place each child at the running main position, offset across by its alignment. This is the
402
509
  * ONE layout a child gets. Measuring, it only sizes - and reuses a size already measured under the same
403
510
  * constraints, or the measuring itself would double at every level.
404
511
  *
405
512
  * `stretch` caps a child's cross to `crossExtent`, not `crossAvail`, so siblings end up equal across the
406
513
  * axis. A child may override the container with its own `alignSelf` (CSS).
407
514
  */
408
- function placeLine(line, cross, crossExtent, crossOrigin, spacing, weight, remaining, mode) {
515
+ function placeLine(line, cross, crossExtent, crossOrigin, spacing, mode) {
409
516
  const { axis, ctx } = line;
410
517
  const place = (child, constraints, offset) => {
411
518
  if (mode === "layout")
@@ -421,21 +528,22 @@ function placeLine(line, cross, crossExtent, crossOrigin, spacing, weight, remai
421
528
  line.children.forEach((child, index) => {
422
529
  let mainExtent;
423
530
  const flexible = flexibleOf(child);
531
+ // A region-sized child (a Flow) takes the height the Row was given, never one its siblings make.
532
+ const regionSized = axis.mainHorizontal && child.needsRegion();
424
533
  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));
534
+ mainExtent = line.flexExtent.get(child);
535
+ const align = alignSelfOf(child) ?? cross;
536
+ // Stretched, it is capped at the line; otherwise it may take what the line offers, and is
537
+ // aligned by what it takes.
538
+ const cap = align === "stretch" && !regionSized ? crossExtent : line.crossAvail;
539
+ const own = align === "stretch" || regionSized ? cap : ownCross(line, child, mainExtent);
540
+ const size = place(child, axis.flexConstraints(mainExtent, cap), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, own)));
433
541
  placedCross = Math.max(placedCross, axis.crossOf(size));
434
542
  }
435
543
  else {
436
544
  const align = alignSelfOf(child) ?? cross;
437
545
  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)));
546
+ const size = place(child, axis.measureConstraints(align === "stretch" && !regionSized ? crossExtent : line.crossAvail, mainCapFor(line, child)), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, childCross)));
439
547
  mainExtent = axis.mainOf(size);
440
548
  placedCross = Math.max(placedCross, axis.crossOf(size));
441
549
  }
@@ -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;