@dloizides/poster-render 0.2.1 → 0.2.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ - Arch flanks: both flank price groups use one size per line, so "per hour" and "Party pass" match.
6
+ - Two-column bodies: the break is chosen from heights measured at the column width, and the shorter column spreads its group gaps so both columns end together.
7
+ - Footer base size 38 (was 34): includes and footer stay at 30 px or more on story and phone arch layouts.
8
+ - `picture-bottom`: the picture region starts 40u under the body's measured end, spans the middle 80% of the width and runs to the frame edge.
9
+ - Venue gets a rule below as well as above (`FlowGroup.ruleBelow`).
10
+
3
11
  ## 0.2.1
4
12
 
5
13
  - Venue pin: an accent disc with a pin glyph beside the venue lines; `RenderReport.iconBoxes`.
package/dist/index.js CHANGED
@@ -347,7 +347,7 @@ function bodyGroups(doc, p, u, withPrices = true) {
347
347
  const groups = [];
348
348
  const isList = doc.layout === "list" /* List */;
349
349
  if (b.venue) {
350
- groups.push({ lines: b.venue.map((v) => l.venue?.(v, isList ? 48 : 58)), gapAfter: 40 * u, rule: { color: p.accent, height: 8 * u }, icon: { color: p.accent, glyph: p.background } });
350
+ groups.push({ lines: b.venue.map((v) => l.venue?.(v, isList ? 48 : 58)), gapAfter: 40 * u, rule: { color: p.accent, height: 8 * u }, ruleBelow: true, icon: { color: p.accent, glyph: p.background } });
351
351
  }
352
352
  groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
353
353
  if (b.includes) {
@@ -355,7 +355,7 @@ function bodyGroups(doc, p, u, withPrices = true) {
355
355
  groups.push({ lines, gapAfter: 24 * u, uniform: true });
356
356
  }
357
357
  if (b.footer) {
358
- groups.push({ lines: [l.soft?.(b.footer, 34)], gapAfter: 0 });
358
+ groups.push({ lines: [l.soft?.(b.footer, 38)], gapAfter: 0 });
359
359
  }
360
360
  return groups;
361
361
  }
@@ -550,18 +550,24 @@ function placeGroup(m, k, col, y0, out) {
550
550
  });
551
551
  y = placeIcon(group, d, col.x + pad, top + pad, y, out);
552
552
  y += pad;
553
+ if (group.rule && group.ruleBelow) {
554
+ y += group.rule.height * k * (RULE_GAP - 1);
555
+ out?.rules.push({ rect: { x: col.x, y, w: col.w * RULE_WIDTH, h: group.rule.height * k }, color: group.rule.color });
556
+ y += group.rule.height * k;
557
+ }
553
558
  if (group.card) {
554
559
  out?.cards.unshift({ rect: { x: col.x, y: top, w: col.w, h: y - top }, color: group.card.color });
555
560
  }
556
561
  return y + group.gapAfter * k - y0;
557
562
  }
558
- function placeColumn(groups, k, col, out) {
563
+ function placeColumn(groups, k, col, out, spread = 0) {
559
564
  let h = 0;
560
565
  for (const g of groups) {
561
- h += placeGroup(g, k, col, col.y + h, out);
566
+ h += placeGroup(g, k, col, col.y + h, out) + spread;
562
567
  }
563
- return h;
568
+ return h - spread;
564
569
  }
570
+ var columnSum = (heights, from, to) => heights.slice(from, to).reduce((a, b) => a + b, 0);
565
571
  var tallest = (heights, cut) => {
566
572
  const left = heights.slice(0, cut).reduce((a, b) => a + b, 0);
567
573
  const right = heights.slice(cut).reduce((a, b) => a + b, 0);
@@ -587,19 +593,17 @@ function splitColumns(groups, zone) {
587
593
  if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
588
594
  return [{ col: zone, groups }];
589
595
  }
590
- const heights = groups.map((g) => placeGroup(g, 1, zone, 0, null));
591
- const half = heights.reduce((a, b) => a + b, 0) / 2;
592
- let acc = 0;
593
- let cut = 1;
594
- heights.forEach((h, i) => {
595
- if (acc < half) {
596
- cut = i + 1;
597
- }
598
- acc += h;
599
- });
600
- cut = keepTogether(groups, heights, Math.min(cut, groups.length - 1));
601
596
  const gap = Math.round(zone.w * 0.04);
602
597
  const w = (zone.w - gap) / 2;
598
+ const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
599
+ const total = columnSum(heights, 0, heights.length);
600
+ let cut = 1;
601
+ for (let c = 2; c < groups.length; c++) {
602
+ if (Math.abs(total - 2 * columnSum(heights, 0, c)) < Math.abs(total - 2 * columnSum(heights, 0, cut))) {
603
+ cut = c;
604
+ }
605
+ }
606
+ cut = keepTogether(groups, heights, cut);
603
607
  return [
604
608
  { col: { ...zone, w }, groups: groups.slice(0, cut) },
605
609
  { col: { ...zone, x: zone.x + w + gap, w }, groups: groups.slice(cut) }
@@ -629,7 +633,9 @@ function flow(ctx, groups, zone, fonts) {
629
633
  }
630
634
  }
631
635
  const out = emptyFlow();
632
- columns.forEach((c) => placeColumn(c.groups, lo, c.col, out));
636
+ const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
637
+ const end = Math.max(...heights);
638
+ columns.forEach((c, i) => placeColumn(c.groups, lo, c.col, out, c.groups.length > 1 && columns.length > 1 ? (end - (heights[i] ?? end)) / (c.groups.length - 1) : 0));
633
639
  const lines = groups.flatMap((g) => g.lines);
634
640
  out.texts.forEach((t, i) => {
635
641
  const line = lines[i];
@@ -638,6 +644,44 @@ function flow(ctx, groups, zone, fonts) {
638
644
  return out;
639
645
  }
640
646
 
647
+ // src/render/regions.ts
648
+ var LINE_HEIGHT2 = 1.2;
649
+ var BODY_GAP = 40;
650
+ var BOTTOM_SIDE = 0.1;
651
+ function flowFlanks(ctx, groups, flanks, fonts) {
652
+ const half = Math.ceil(groups.length / 2);
653
+ return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
654
+ }
655
+ function smallestPerLine(groups, results) {
656
+ const sizes = results.flatMap((r3) => r3.texts.map((t) => t.box.h / LINE_HEIGHT2));
657
+ const smallest = [];
658
+ let i = 0;
659
+ groups.forEach(
660
+ (g) => g.lines.forEach((_line, j) => {
661
+ smallest[j] = Math.min(smallest[j] ?? Infinity, sizes[i] ?? Infinity);
662
+ i++;
663
+ })
664
+ );
665
+ return smallest;
666
+ }
667
+ function equalFlanks(ctx, groups, flanks, fonts) {
668
+ const smallest = smallestPerLine(groups, flowFlanks(ctx, groups, flanks, fonts));
669
+ const sized = groups.map((g) => ({ ...g, lines: g.lines.map((line, j) => ({ ...line, size: Math.min(line.size, smallest[j] ?? line.size) })) }));
670
+ return flowFlanks(ctx, sized, flanks, fonts);
671
+ }
672
+ function measuredEnd(f, fallback) {
673
+ const bottoms = [...f.texts.map((t) => t.box), ...f.rules.map((r3) => r3.rect), ...f.icons.map((i) => i.rect)].map((r3) => r3.y + r3.h);
674
+ return bottoms.length > 0 ? Math.max(...bottoms) : fallback;
675
+ }
676
+ function pictureRegion(layout, geo, body, u, frame) {
677
+ if (layout !== "picture-bottom" /* PictureBottom */) {
678
+ return geo.picture;
679
+ }
680
+ const y = Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u);
681
+ const x = Math.round(frame.width * BOTTOM_SIDE);
682
+ return { x, y, w: frame.width - 2 * x, h: frame.height - y };
683
+ }
684
+
641
685
  // src/render/render.ts
642
686
  var BASE = 1080;
643
687
  var LOGO_SHARE = 0.26;
@@ -730,10 +774,6 @@ function drawArch(ctx, r3, color) {
730
774
  ctx.arc(r3.x + rad, r3.y + rad, rad, Math.PI, Math.PI * 2);
731
775
  ctx.fill();
732
776
  }
733
- function flankFlows(ctx, groups, flanks, fonts) {
734
- const half = Math.ceil(groups.length / 2);
735
- return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
736
- }
737
777
  function capAligned(zone, head) {
738
778
  const first = head.texts[0]?.box;
739
779
  const shift = first ? Math.min(Math.max(0, Math.round(first.y + first.h * CAP_TOP) - zone.y), Math.round(zone.h / 2)) : 0;
@@ -767,7 +807,7 @@ function render(input, assets, canvas) {
767
807
  const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
768
808
  const flanks = geo.flanks ?? [];
769
809
  const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
770
- const sides = flanks.length > 0 ? flankFlows(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
810
+ const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
771
811
  const radius = Math.min(width, height) * CARD_RADIUS;
772
812
  [head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
773
813
  const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
@@ -780,7 +820,8 @@ function render(input, assets, canvas) {
780
820
  if (backdrop) {
781
821
  drawArch(ctx, backdrop, palette.arch);
782
822
  }
783
- const picture = image && doc.picture ? placeContain(image, geo.picture, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
823
+ const region = pictureRegion(doc.layout, geo, body, u, { width, height });
824
+ const picture = image && doc.picture ? placeContain(image, region, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
784
825
  if (image && picture) {
785
826
  ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
786
827
  }
@@ -788,7 +829,7 @@ function render(input, assets, canvas) {
788
829
  width,
789
830
  height,
790
831
  picture,
791
- pictureRegion: geo.picture,
832
+ pictureRegion: region,
792
833
  textBoxes: [head, body, ...sides].flatMap((f) => f.texts.map((t) => t.box)),
793
834
  cardBoxes: body.cards.map((c) => c.rect),
794
835
  iconBoxes: [head, body, ...sides].flatMap((f) => f.icons.map((i) => i.rect)),