@dloizides/poster-render 0.2.1 → 0.2.3

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3
4
+
5
+ - `picture-bottom`: the picture is sized for a tall cut-out (design aspect 0.57): 80% of the width, standing on the bottom edge; the header and body share the top (at least 25% of the height) and shrink to fit. The picture is never cropped.
6
+
7
+ ## 0.2.2
8
+
9
+ - Arch flanks: both flank price groups use one size per line, so "per hour" and "Party pass" match.
10
+ - 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.
11
+ - Footer base size 38 (was 34): includes and footer stay at 30 px or more on story and phone arch layouts.
12
+ - `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.
13
+ - Venue gets a rule below as well as above (`FlowGroup.ruleBelow`).
14
+
3
15
  ## 0.2.1
4
16
 
5
17
  - 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
  }
@@ -409,8 +409,11 @@ var toRect = (f, w, h) => {
409
409
  var MARGIN = 0.045;
410
410
  var HEADER_PORTRAIT = 0.3;
411
411
  var HEADER_LANDSCAPE = 0.36;
412
- var BOTTOM_HEADER = 0.2;
413
- var BOTTOM_PICTURE = 0.52;
412
+ var BOTTOM_PICTURE_W = 0.8;
413
+ var BOTTOM_MIN_TOP = 0.25;
414
+ var BOTTOM_HEADER_SHARE = 0.45;
415
+ var BOTTOM_GAP = 40 / 1080;
416
+ var BOTTOM_ASPECT = 0.57;
414
417
  var PICTURE_SIDE = {
415
418
  ["picture-right" /* PictureRight */]: [0.6, 0.42],
416
419
  ["picture-left" /* PictureLeft */]: [0.6, 0.42],
@@ -441,12 +444,15 @@ function side(w, h, fraction) {
441
444
  }
442
445
  function bottom(w, h) {
443
446
  const m = r2(Math.min(w, h) * MARGIN);
444
- const top = r2(h * BOTTOM_HEADER);
445
- const pic = r2(h * BOTTOM_PICTURE);
447
+ const gap = r2(Math.min(w, h) * BOTTOM_GAP);
448
+ const picW = r2(w * BOTTOM_PICTURE_W);
449
+ const picH = Math.min(r2(picW / BOTTOM_ASPECT), h - r2(h * BOTTOM_MIN_TOP));
450
+ const pic = h - picH;
451
+ const top = m + r2((pic - m) * BOTTOM_HEADER_SHARE);
446
452
  return {
447
- header: { x: m, y: m, w: w - 2 * m, h: top - 2 * m },
448
- body: { x: m, y: top, w: w - 2 * m, h: pic - top - m },
449
- picture: { x: 0, y: pic, w, h: h - pic },
453
+ header: { x: m, y: m, w: w - 2 * m, h: top - m },
454
+ body: { x: m, y: top, w: w - 2 * m, h: pic - gap - top },
455
+ picture: { x: r2((w - picW) / 2), y: pic, w: picW, h: picH },
450
456
  alignX: 0.5
451
457
  };
452
458
  }
@@ -550,18 +556,24 @@ function placeGroup(m, k, col, y0, out) {
550
556
  });
551
557
  y = placeIcon(group, d, col.x + pad, top + pad, y, out);
552
558
  y += pad;
559
+ if (group.rule && group.ruleBelow) {
560
+ y += group.rule.height * k * (RULE_GAP - 1);
561
+ out?.rules.push({ rect: { x: col.x, y, w: col.w * RULE_WIDTH, h: group.rule.height * k }, color: group.rule.color });
562
+ y += group.rule.height * k;
563
+ }
553
564
  if (group.card) {
554
565
  out?.cards.unshift({ rect: { x: col.x, y: top, w: col.w, h: y - top }, color: group.card.color });
555
566
  }
556
567
  return y + group.gapAfter * k - y0;
557
568
  }
558
- function placeColumn(groups, k, col, out) {
569
+ function placeColumn(groups, k, col, out, spread = 0) {
559
570
  let h = 0;
560
571
  for (const g of groups) {
561
- h += placeGroup(g, k, col, col.y + h, out);
572
+ h += placeGroup(g, k, col, col.y + h, out) + spread;
562
573
  }
563
- return h;
574
+ return h - spread;
564
575
  }
576
+ var columnSum = (heights, from, to) => heights.slice(from, to).reduce((a, b) => a + b, 0);
565
577
  var tallest = (heights, cut) => {
566
578
  const left = heights.slice(0, cut).reduce((a, b) => a + b, 0);
567
579
  const right = heights.slice(cut).reduce((a, b) => a + b, 0);
@@ -587,19 +599,17 @@ function splitColumns(groups, zone) {
587
599
  if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
588
600
  return [{ col: zone, groups }];
589
601
  }
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
602
  const gap = Math.round(zone.w * 0.04);
602
603
  const w = (zone.w - gap) / 2;
604
+ const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
605
+ const total = columnSum(heights, 0, heights.length);
606
+ let cut = 1;
607
+ for (let c = 2; c < groups.length; c++) {
608
+ if (Math.abs(total - 2 * columnSum(heights, 0, c)) < Math.abs(total - 2 * columnSum(heights, 0, cut))) {
609
+ cut = c;
610
+ }
611
+ }
612
+ cut = keepTogether(groups, heights, cut);
603
613
  return [
604
614
  { col: { ...zone, w }, groups: groups.slice(0, cut) },
605
615
  { col: { ...zone, x: zone.x + w + gap, w }, groups: groups.slice(cut) }
@@ -629,7 +639,9 @@ function flow(ctx, groups, zone, fonts) {
629
639
  }
630
640
  }
631
641
  const out = emptyFlow();
632
- columns.forEach((c) => placeColumn(c.groups, lo, c.col, out));
642
+ const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
643
+ const end = Math.max(...heights);
644
+ 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
645
  const lines = groups.flatMap((g) => g.lines);
634
646
  out.texts.forEach((t, i) => {
635
647
  const line = lines[i];
@@ -638,6 +650,42 @@ function flow(ctx, groups, zone, fonts) {
638
650
  return out;
639
651
  }
640
652
 
653
+ // src/render/regions.ts
654
+ var LINE_HEIGHT2 = 1.2;
655
+ var BODY_GAP = 40;
656
+ function flowFlanks(ctx, groups, flanks, fonts) {
657
+ const half = Math.ceil(groups.length / 2);
658
+ return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
659
+ }
660
+ function smallestPerLine(groups, results) {
661
+ const sizes = results.flatMap((r3) => r3.texts.map((t) => t.box.h / LINE_HEIGHT2));
662
+ const smallest = [];
663
+ let i = 0;
664
+ groups.forEach(
665
+ (g) => g.lines.forEach((_line, j) => {
666
+ smallest[j] = Math.min(smallest[j] ?? Infinity, sizes[i] ?? Infinity);
667
+ i++;
668
+ })
669
+ );
670
+ return smallest;
671
+ }
672
+ function equalFlanks(ctx, groups, flanks, fonts) {
673
+ const smallest = smallestPerLine(groups, flowFlanks(ctx, groups, flanks, fonts));
674
+ const sized = groups.map((g) => ({ ...g, lines: g.lines.map((line, j) => ({ ...line, size: Math.min(line.size, smallest[j] ?? line.size) })) }));
675
+ return flowFlanks(ctx, sized, flanks, fonts);
676
+ }
677
+ function measuredEnd(f, fallback) {
678
+ 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);
679
+ return bottoms.length > 0 ? Math.max(...bottoms) : fallback;
680
+ }
681
+ function pictureRegion(layout, geo, body, u, frame) {
682
+ if (layout !== "picture-bottom" /* PictureBottom */) {
683
+ return geo.picture;
684
+ }
685
+ const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u));
686
+ return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
687
+ }
688
+
641
689
  // src/render/render.ts
642
690
  var BASE = 1080;
643
691
  var LOGO_SHARE = 0.26;
@@ -730,10 +778,6 @@ function drawArch(ctx, r3, color) {
730
778
  ctx.arc(r3.x + rad, r3.y + rad, rad, Math.PI, Math.PI * 2);
731
779
  ctx.fill();
732
780
  }
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
781
  function capAligned(zone, head) {
738
782
  const first = head.texts[0]?.box;
739
783
  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 +811,7 @@ function render(input, assets, canvas) {
767
811
  const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
768
812
  const flanks = geo.flanks ?? [];
769
813
  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()];
814
+ const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
771
815
  const radius = Math.min(width, height) * CARD_RADIUS;
772
816
  [head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
773
817
  const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
@@ -780,7 +824,8 @@ function render(input, assets, canvas) {
780
824
  if (backdrop) {
781
825
  drawArch(ctx, backdrop, palette.arch);
782
826
  }
783
- const picture = image && doc.picture ? placeContain(image, geo.picture, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
827
+ const region = pictureRegion(doc.layout, geo, body, u, { height });
828
+ const picture = image && doc.picture ? placeContain(image, region, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
784
829
  if (image && picture) {
785
830
  ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
786
831
  }
@@ -788,7 +833,7 @@ function render(input, assets, canvas) {
788
833
  width,
789
834
  height,
790
835
  picture,
791
- pictureRegion: geo.picture,
836
+ pictureRegion: region,
792
837
  textBoxes: [head, body, ...sides].flatMap((f) => f.texts.map((t) => t.box)),
793
838
  cardBoxes: body.cards.map((c) => c.rect),
794
839
  iconBoxes: [head, body, ...sides].flatMap((f) => f.icons.map((i) => i.rect)),