@dloizides/poster-render 0.2.0 → 0.2.1

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.1
4
+
5
+ - Venue pin: an accent disc with a pin glyph beside the venue lines; `RenderReport.iconBoxes`.
6
+ - Includes block drawn as a heading plus bullets, with no card.
7
+ - Price groups stay in one column when a wide body splits into two (`FlowGroup.keepWithNext`).
8
+ - `desktop-safe` landscape gets an explicit logo rect beside the title; `desktop-arch` logo enlarged and no longer pushed down to the title cap; `RenderReport.logo`.
9
+ - List rows: date at 0.7x, 48 px between dated rows.
10
+
3
11
  ## 0.2.0
4
12
 
5
13
  - Layouts `arch`, `phone-clock-safe`, `phone-arch`, `desktop-safe`, `desktop-arch` (FLIER-1 D4 "Keep all layouts"); `LAYOUT_CATALOGUE` + `defaultLayoutFor`.
package/dist/index.d.mts CHANGED
@@ -140,6 +140,8 @@ interface RenderReport {
140
140
  pictureRegion: Rect;
141
141
  textBoxes: Rect[];
142
142
  cardBoxes: Rect[];
143
+ iconBoxes: Rect[];
144
+ logo: Rect | null;
143
145
  backdrop: Rect | null;
144
146
  }
145
147
  interface Palette {
package/dist/index.d.ts CHANGED
@@ -140,6 +140,8 @@ interface RenderReport {
140
140
  pictureRegion: Rect;
141
141
  textBoxes: Rect[];
142
142
  cardBoxes: Rect[];
143
+ iconBoxes: Rect[];
144
+ logo: Rect | null;
143
145
  backdrop: Rect | null;
144
146
  }
145
147
  interface Palette {
package/dist/index.js CHANGED
@@ -314,18 +314,19 @@ function headerGroups(doc, p, u) {
314
314
  return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
315
315
  }
316
316
  function itemGroup(item, l, u) {
317
- const lines = [l.script?.(item.date, 96)];
317
+ const lines = [l.script?.(item.date, 67)];
318
318
  item.schedule.forEach((row) => lines.push(l.strong?.(`${row.label} ${row.time}`, 42)));
319
319
  if (item.price) {
320
320
  lines.push(l.accent?.(item.price, 42));
321
321
  }
322
- return { lines, gapAfter: 26 * u };
322
+ return { lines, gapAfter: 48 * u };
323
323
  }
324
324
  function eventGroups(doc, l, u, withPrices) {
325
325
  const b = doc.blocks;
326
326
  const groups = [];
327
327
  b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
328
- (withPrices ? b.prices : void 0)?.forEach((row) => groups.push({ lines: [l.strong?.(row.amount, 66), l.accent?.(row.label, 52)], gapAfter: 30 * u }));
328
+ const prices = (withPrices ? b.prices : void 0) ?? [];
329
+ prices.forEach((row, i) => groups.push({ lines: [l.strong?.(row.amount, 66), l.accent?.(row.label, 52)], gapAfter: 30 * u, keepWithNext: i < prices.length - 1 }));
329
330
  return groups;
330
331
  }
331
332
  function listGroups(doc, l, u) {
@@ -346,12 +347,12 @@ function bodyGroups(doc, p, u, withPrices = true) {
346
347
  const groups = [];
347
348
  const isList = doc.layout === "list" /* List */;
348
349
  if (b.venue) {
349
- groups.push({ lines: b.venue.map((v) => l.venue?.(v, isList ? 48 : 58)), gapAfter: 40 * u, rule: { color: p.accent, height: 8 * u } });
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
351
  }
351
352
  groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
352
353
  if (b.includes) {
353
354
  const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
354
- groups.push({ lines, gapAfter: 24 * u, card: { color: p.card, pad: 28 * u }, uniform: true });
355
+ groups.push({ lines, gapAfter: 24 * u, uniform: true });
355
356
  }
356
357
  if (b.footer) {
357
358
  groups.push({ lines: [l.soft?.(b.footer, 34)], gapAfter: 0 });
@@ -390,11 +391,11 @@ var LAYOUT_SPECS = {
390
391
  },
391
392
  ["desktop-safe" /* DesktopSafe */]: {
392
393
  portrait: { header: [0.08, 0.06, 0.84, 0.24], body: [0.08, 0.31, 0.46, 0.58], picture: [0.56, 0.36, 0.44, 0.64], alignX: 1 },
393
- landscape: { header: [0.0625, 0.05, 0.58, 0.44], body: [0.0625, 0.5, 0.58, 0.38], picture: [0.67, 0.0972, 0.29, 0.9028], alignX: 1 }
394
+ landscape: { header: [0.0625, 0.05, 0.58, 0.44], body: [0.0625, 0.5, 0.58, 0.38], picture: [0.67, 0.0972, 0.29, 0.9028], alignX: 1, logo: [0.4, 0.045, 0.09, 0.17] }
394
395
  },
395
396
  ["desktop-arch" /* DesktopArch */]: {
396
397
  portrait: { header: [0.08, 0.06, 0.84, 0.2], body: [0.08, 0.27, 0.84, 0.2], picture: [0.14, 0.48, 0.72, 0.52], alignX: 0.5, arch: 0.05 },
397
- landscape: { header: [0.07, 0.3, 0.26, 0.45], body: [0.7, 0.25, 0.27, 0.65], picture: DESK_ARCH_PIC, alignX: 0.5, logo: [0.88, 0.04, 0.09, 0.14], arch: 0.05 }
398
+ landscape: { header: [0.07, 0.3, 0.26, 0.45], body: [0.7, 0.25, 0.27, 0.65], picture: DESK_ARCH_PIC, alignX: 0.5, logo: [0.86, 0.03, 0.11, 0.18], arch: 0.05 }
398
399
  }
399
400
  };
400
401
  var r = Math.round;
@@ -496,8 +497,14 @@ var RULE_WIDTH = 0.9;
496
497
  var WIDE = 1.6;
497
498
  var MIN_K = 0.02;
498
499
  var STEPS = 22;
500
+ var ICON_SCALE = 1.6;
501
+ var ICON_GAP = 0.35;
499
502
  var fontOf = (line, size, fonts) => `${line.weight} ${size}px ${fonts[line.role]}`;
500
- var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [] });
503
+ var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [], icons: [] });
504
+ var iconSize = (group, k) => {
505
+ const first = group.lines[0];
506
+ return group.icon && first ? first.size * k * LINE_HEIGHT * ICON_SCALE : 0;
507
+ };
501
508
  function lineSize(line, width, k, avail) {
502
509
  const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
503
510
  return Math.max(0, Math.min(line.size * k, line.size * (avail - indent) / Math.max(width, 1)));
@@ -508,6 +515,14 @@ function groupRatio(m, k, avail) {
508
515
  }
509
516
  return Math.min(...m.group.lines.map((line, i) => lineSize(line, m.widths[i] ?? 1, k, avail) / line.size));
510
517
  }
518
+ function placeIcon(group, d, x, textTop, textBottom, out) {
519
+ if (!group.icon || d <= 0) {
520
+ return textBottom;
521
+ }
522
+ const textH = textBottom - textTop;
523
+ out?.icons.push({ rect: { x, y: textTop + Math.max(0, (textH - d) / 2), w: d, h: d }, color: group.icon.color, glyph: group.icon.glyph });
524
+ return textTop + Math.max(textH, d);
525
+ }
511
526
  function placeGroup(m, k, col, y0, out) {
512
527
  const { group } = m;
513
528
  let y = y0;
@@ -518,10 +533,13 @@ function placeGroup(m, k, col, y0, out) {
518
533
  const pad = group.card ? group.card.pad * k : 0;
519
534
  const top = y;
520
535
  y += pad;
521
- const ratio = groupRatio(m, k, col.w - 2 * pad);
536
+ const d = iconSize(group, k);
537
+ const iconIndent = d > 0 ? d * (1 + ICON_GAP) : 0;
538
+ const avail = col.w - 2 * pad - iconIndent;
539
+ const ratio = groupRatio(m, k, avail);
522
540
  group.lines.forEach((line, i) => {
523
- const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, col.w - 2 * pad) : line.size * ratio;
524
- const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
541
+ const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, avail) : line.size * ratio;
542
+ const indent = (line.bullet ? line.size * k * BULLET_INDENT : 0) + iconIndent;
525
543
  const w = (m.widths[i] ?? 0) * size / line.size;
526
544
  const x = col.right ? col.x + col.w - pad - w : col.x + pad + indent;
527
545
  out?.texts.push({ text: line.text, font: "", color: line.color, box: { x, y, w, h: size * LINE_HEIGHT } });
@@ -530,6 +548,7 @@ function placeGroup(m, k, col, y0, out) {
530
548
  }
531
549
  y += size * LINE_HEIGHT;
532
550
  });
551
+ y = placeIcon(group, d, col.x + pad, top + pad, y, out);
533
552
  y += pad;
534
553
  if (group.card) {
535
554
  out?.cards.unshift({ rect: { x: col.x, y: top, w: col.w, h: y - top }, color: group.card.color });
@@ -543,6 +562,27 @@ function placeColumn(groups, k, col, out) {
543
562
  }
544
563
  return h;
545
564
  }
565
+ var tallest = (heights, cut) => {
566
+ const left = heights.slice(0, cut).reduce((a, b) => a + b, 0);
567
+ const right = heights.slice(cut).reduce((a, b) => a + b, 0);
568
+ return Math.max(left, right);
569
+ };
570
+ function keepTogether(groups, heights, cut) {
571
+ const breaks = (c) => groups[c - 1]?.group.keepWithNext === true;
572
+ let back = cut;
573
+ while (back > 1 && breaks(back)) {
574
+ back--;
575
+ }
576
+ let fwd = cut;
577
+ while (fwd < groups.length - 1 && breaks(fwd)) {
578
+ fwd++;
579
+ }
580
+ const candidates = [back, fwd].filter((c) => !breaks(c));
581
+ if (candidates.length === 0) {
582
+ return cut;
583
+ }
584
+ return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
585
+ }
546
586
  function splitColumns(groups, zone) {
547
587
  if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
548
588
  return [{ col: zone, groups }];
@@ -557,7 +597,7 @@ function splitColumns(groups, zone) {
557
597
  }
558
598
  acc += h;
559
599
  });
560
- cut = Math.min(cut, groups.length - 1);
600
+ cut = keepTogether(groups, heights, Math.min(cut, groups.length - 1));
561
601
  const gap = Math.round(zone.w * 0.04);
562
602
  const w = (zone.w - gap) / 2;
563
603
  return [
@@ -603,6 +643,32 @@ var BASE = 1080;
603
643
  var LOGO_SHARE = 0.26;
604
644
  var CARD_RADIUS = 0.035;
605
645
  var CAP_TOP = 0.15;
646
+ var PIN_HEAD_Y = 0.08;
647
+ var PIN_HEAD_R = 0.19;
648
+ var PIN_TIP_Y = 0.28;
649
+ var PIN_HOLE = 0.42;
650
+ var ARC_START = 0.75;
651
+ var ARC_END = 2.25;
652
+ function drawPin(ctx, r3, color, glyph) {
653
+ const cx = r3.x + r3.w / 2;
654
+ const cy = r3.y + r3.h / 2;
655
+ const hy = cy - r3.w * PIN_HEAD_Y;
656
+ const rad = r3.w * PIN_HEAD_R;
657
+ ctx.fillStyle = color;
658
+ ctx.beginPath();
659
+ ctx.arc(cx, cy, r3.w / 2, 0, Math.PI * 2);
660
+ ctx.fill();
661
+ ctx.fillStyle = glyph;
662
+ ctx.beginPath();
663
+ ctx.moveTo(cx, cy + r3.w * PIN_TIP_Y);
664
+ ctx.arc(cx, hy, rad, Math.PI * ARC_START, Math.PI * ARC_END);
665
+ ctx.closePath();
666
+ ctx.fill();
667
+ ctx.fillStyle = color;
668
+ ctx.beginPath();
669
+ ctx.arc(cx, hy, rad * PIN_HOLE, 0, Math.PI * 2);
670
+ ctx.fill();
671
+ }
606
672
  function roundRect(ctx, r3, radius) {
607
673
  const rad = Math.min(radius, r3.w / 2, r3.h / 2);
608
674
  ctx.beginPath();
@@ -629,6 +695,7 @@ function drawFlow(ctx, f, radius) {
629
695
  ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
630
696
  ctx.fill();
631
697
  });
698
+ f.icons.forEach((i) => drawPin(ctx, i.rect, i.color, i.glyph));
632
699
  ctx.textAlign = "left";
633
700
  ctx.textBaseline = "top";
634
701
  f.texts.forEach((t) => {
@@ -704,9 +771,9 @@ function render(input, assets, canvas) {
704
771
  const radius = Math.min(width, height) * CARD_RADIUS;
705
772
  [head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
706
773
  const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
707
- if (logoImage) {
708
- const r3 = placeContain(logoImage, capAligned(zones.logo, head), "top" /* Top */, 1, 1);
709
- ctx.drawImage(logoImage, r3.x, r3.y, r3.w, r3.h);
774
+ const logo = logoImage ? placeContain(logoImage, geo.logo ? zones.logo : capAligned(zones.logo, head), "top" /* Top */, 1, 1) : null;
775
+ if (logoImage && logo) {
776
+ ctx.drawImage(logoImage, logo.x, logo.y, logo.w, logo.h);
710
777
  }
711
778
  const image = doc.picture ? assets[doc.picture.imageId] : void 0;
712
779
  const backdrop = image ? archRect(geo) : null;
@@ -724,6 +791,8 @@ function render(input, assets, canvas) {
724
791
  pictureRegion: geo.picture,
725
792
  textBoxes: [head, body, ...sides].flatMap((f) => f.texts.map((t) => t.box)),
726
793
  cardBoxes: body.cards.map((c) => c.rect),
794
+ iconBoxes: [head, body, ...sides].flatMap((f) => f.icons.map((i) => i.rect)),
795
+ logo,
727
796
  backdrop
728
797
  };
729
798
  }