@dloizides/poster-render 0.2.4 → 0.2.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.
package/dist/index.mjs CHANGED
@@ -224,7 +224,7 @@ function checkObject(schema, value, path, errors) {
224
224
  }
225
225
  }
226
226
  for (const [key, child] of Object.entries(value)) {
227
- const childSchema = props[key];
227
+ const childSchema = Object.hasOwn(props, key) ? props[key] : void 0;
228
228
  if (childSchema === void 0) {
229
229
  if (schema.additionalProperties === false) {
230
230
  errors.push(`${join(path, key)}: is not allowed`);
@@ -282,10 +282,10 @@ function validateFlierDoc(input) {
282
282
  }
283
283
  function missingAssets(doc, assets) {
284
284
  const errors = [];
285
- if (doc.picture && assets[doc.picture.imageId] === void 0) {
285
+ if (doc.picture && !Object.hasOwn(assets, doc.picture.imageId)) {
286
286
  errors.push(`picture.imageId: no asset named "${doc.picture.imageId}"`);
287
287
  }
288
- if (doc.logo && assets[doc.logo.imageId] === void 0) {
288
+ if (doc.logo && !Object.hasOwn(assets, doc.logo.imageId)) {
289
289
  errors.push(`logo.imageId: no asset named "${doc.logo.imageId}"`);
290
290
  }
291
291
  return errors;
@@ -345,9 +345,10 @@ var BOTTOM_MIN_TOP = 0.25;
345
345
  var BOTTOM_HEADER_SHARE = 0.3;
346
346
  var BOTTOM_GAP = 40 / 1080;
347
347
  var BOTTOM_ASPECT = 0.57;
348
+ var DEFAULT_SIDE = [0.6, 0.42];
348
349
  var PICTURE_SIDE = {
349
- ["picture-right" /* PictureRight */]: [0.6, 0.42],
350
- ["picture-left" /* PictureLeft */]: [0.6, 0.42],
350
+ ["picture-right" /* PictureRight */]: DEFAULT_SIDE,
351
+ ["picture-left" /* PictureLeft */]: DEFAULT_SIDE,
351
352
  ["list" /* List */]: [0.5, 0.36]
352
353
  };
353
354
  var ANCHOR_Y = { ["top" /* Top */]: 0, ["center" /* Center */]: 0.5, ["bottom" /* Bottom */]: 1 };
@@ -407,13 +408,14 @@ function geometry(layout, w, h, give = 0) {
407
408
  if (layout === "picture-bottom" /* PictureBottom */) {
408
409
  return bottom(w, h, BOTTOM_PICTURE_W - give);
409
410
  }
410
- const g = side(w, h, PICTURE_SIDE[layout] ?? [0.6, 0.42]);
411
+ const g = side(w, h, PICTURE_SIDE[layout] ?? DEFAULT_SIDE);
411
412
  if (layout !== "picture-left" /* PictureLeft */) {
412
413
  return g;
413
414
  }
414
415
  return { header: mirror(g.header, w), body: mirror(g.body, w), picture: mirror(g.picture, w), alignX: 1 - g.alignX };
415
416
  }
416
- function placeContain(image, region, anchor, alignX, scale) {
417
+ function placeContain(image, region, fit) {
418
+ const { anchor, alignX, scale } = fit;
417
419
  const s = Math.min(region.w / image.width, region.h / image.height) * scale;
418
420
  const w = Math.floor(image.width * s);
419
421
  const h = Math.floor(image.height * s);
@@ -430,6 +432,29 @@ var BOLD = 700;
430
432
  var SEMI = 600;
431
433
  var MEDIUM = 500;
432
434
  var SCRIPT = 400;
435
+ var SIZE = {
436
+ title: 250,
437
+ headerScript: 120,
438
+ itemDate: 67,
439
+ itemRow: 42,
440
+ scheduleLabel: 66,
441
+ scheduleTime: 60,
442
+ priceAmount: 66,
443
+ priceLabel: 52,
444
+ listPrice: 58,
445
+ flankAmount: 110,
446
+ flankLabel: 48,
447
+ venue: 58,
448
+ listVenue: 48,
449
+ small: 38
450
+ };
451
+ var GAP = {
452
+ item: 48,
453
+ row: 30,
454
+ venue: 40,
455
+ includes: 24,
456
+ rule: 8
457
+ };
433
458
  var makers = (p, u) => ({
434
459
  display: (text2, size) => ({ text: text2, role: "display", weight: BOLD, size: size * u, color: p.title }),
435
460
  venue: (text2, size) => ({ text: text2, role: "display", weight: MEDIUM, size: size * u, color: p.ink }),
@@ -442,36 +467,36 @@ var makers = (p, u) => ({
442
467
  function headerGroups(doc, p, u) {
443
468
  const l = makers(p, u);
444
469
  const b = doc.blocks;
445
- const lines = [l.display?.(b.title, 250), b.script ? l.script?.(b.script, 120) : void 0, b.titleBottom ? l.display?.(b.titleBottom, 250) : void 0];
470
+ const lines = [l.display(b.title, SIZE.title), b.script ? l.script(b.script, SIZE.headerScript) : void 0, b.titleBottom ? l.display(b.titleBottom, SIZE.title) : void 0];
446
471
  return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
447
472
  }
448
473
  function itemGroup(item, l, u) {
449
- const lines = [l.script?.(item.date, 67)];
450
- item.schedule.forEach((row) => lines.push(l.strong?.(`${row.label} ${row.time}`, 42)));
474
+ const lines = [l.script(item.date, SIZE.itemDate)];
475
+ item.schedule.forEach((row) => lines.push(l.strong(`${row.label} ${row.time}`, SIZE.itemRow)));
451
476
  if (item.price) {
452
- lines.push(l.accent?.(item.price, 42));
477
+ lines.push(l.accent(item.price, SIZE.itemRow));
453
478
  }
454
- return { lines, gapAfter: 48 * u };
479
+ return { lines, gapAfter: GAP.item * u };
455
480
  }
456
481
  function eventGroups(doc, l, u, withPrices) {
457
482
  const b = doc.blocks;
458
483
  const groups = [];
459
- b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
484
+ b.schedule?.forEach((row) => groups.push({ lines: [l.strong(row.label, SIZE.scheduleLabel), l.accent(row.time, SIZE.scheduleTime)], gapAfter: GAP.row * u }));
460
485
  const prices = (withPrices ? b.prices : void 0) ?? [];
461
- 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 }));
486
+ prices.forEach((row, i) => groups.push({ lines: [l.strong(row.amount, SIZE.priceAmount), l.accent(row.label, SIZE.priceLabel)], gapAfter: GAP.row * u, keepWithNext: i < prices.length - 1 }));
462
487
  return groups;
463
488
  }
464
489
  function listGroups(doc, l, u) {
465
490
  const groups = (doc.items ?? []).map((item) => itemGroup(item, l, u));
466
- const prices = doc.blocks.prices?.map((row) => l.strong?.(`${row.amount} ${row.label}`, 58)) ?? [];
491
+ const prices = doc.blocks.prices?.map((row) => l.strong(`${row.amount} ${row.label}`, SIZE.listPrice)) ?? [];
467
492
  if (prices.length > 0) {
468
- groups.push({ lines: prices, gapAfter: 30 * u });
493
+ groups.push({ lines: prices, gapAfter: GAP.row * u });
469
494
  }
470
495
  return groups;
471
496
  }
472
497
  function flankPriceGroups(doc, p, u) {
473
498
  const l = makers(p, u);
474
- return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong?.(row.amount, 110), l.accent?.(row.label, 48)], gapAfter: 30 * u }));
499
+ return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong(row.amount, SIZE.flankAmount), l.accent(row.label, SIZE.flankLabel)], gapAfter: GAP.row * u }));
475
500
  }
476
501
  function bodyGroups(doc, p, u, withPrices = true) {
477
502
  const l = makers(p, u);
@@ -479,15 +504,16 @@ function bodyGroups(doc, p, u, withPrices = true) {
479
504
  const groups = [];
480
505
  const isList = doc.layout === "list" /* List */;
481
506
  if (b.venue) {
482
- 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 } });
507
+ const size = isList ? SIZE.listVenue : SIZE.venue;
508
+ groups.push({ lines: b.venue.map((v) => l.venue(v, size)), gapAfter: GAP.venue * u, rule: { color: p.accent, height: GAP.rule * u }, ruleBelow: true, icon: { color: p.accent, glyph: p.background } });
483
509
  }
484
510
  groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
485
511
  if (b.includes) {
486
- const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
487
- groups.push({ lines, gapAfter: 24 * u, uniform: true });
512
+ const lines = [l.accent(b.includes.heading, SIZE.small), ...b.includes.items.map((i) => l.item(i, SIZE.small))];
513
+ groups.push({ lines, gapAfter: GAP.includes * u, uniform: true });
488
514
  }
489
515
  if (b.footer) {
490
- groups.push({ lines: [l.soft?.(b.footer, 38)], gapAfter: 0 });
516
+ groups.push({ lines: [l.soft(b.footer, SIZE.small)], gapAfter: 0 });
491
517
  }
492
518
  return groups;
493
519
  }
@@ -499,6 +525,7 @@ var BULLET_RADIUS = 0.2;
499
525
  var RULE_GAP = 2.5;
500
526
  var RULE_WIDTH = 0.9;
501
527
  var WIDE = 1.6;
528
+ var COLUMN_GAP = 0.04;
502
529
  var TWO_COLUMN_MIN = 0.75;
503
530
  var TWO_COLUMN_GAIN = 1.15;
504
531
  var MIN_K = 0.02;
@@ -521,15 +548,17 @@ function groupRatio(m, k, avail) {
521
548
  }
522
549
  return Math.min(...m.group.lines.map((line, i) => lineSize(line, m.widths[i] ?? 1, k, avail) / line.size));
523
550
  }
524
- function placeIcon(group, d, x, textTop, textBottom, out) {
551
+ function placeIcon(group, box, out) {
552
+ const { d, x, top, bottom: bottom2 } = box;
525
553
  if (!group.icon || d <= 0) {
526
- return textBottom;
554
+ return bottom2;
527
555
  }
528
- const textH = textBottom - textTop;
529
- 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 });
530
- return textTop + Math.max(textH, d);
556
+ const textH = bottom2 - top;
557
+ out?.icons.push({ rect: { x, y: top + Math.max(0, (textH - d) / 2), w: d, h: d }, color: group.icon.color, glyph: group.icon.glyph });
558
+ return top + Math.max(textH, d);
531
559
  }
532
- function placeGroup(m, k, col, y0, out) {
560
+ function placeGroup(m, at, out) {
561
+ const { k, col, y0 } = at;
533
562
  const { group } = m;
534
563
  let y = y0;
535
564
  if (group.rule) {
@@ -554,7 +583,7 @@ function placeGroup(m, k, col, y0, out) {
554
583
  }
555
584
  y += size * LINE_HEIGHT;
556
585
  });
557
- y = placeIcon(group, d, col.x + pad, top + pad, y, out);
586
+ y = placeIcon(group, { d, x: col.x + pad, top: top + pad, bottom: y }, out);
558
587
  y += pad;
559
588
  if (group.rule && group.ruleBelow) {
560
589
  y += group.rule.height * k * (RULE_GAP - 1);
@@ -566,10 +595,11 @@ function placeGroup(m, k, col, y0, out) {
566
595
  }
567
596
  return y + group.gapAfter * k - y0;
568
597
  }
569
- function placeColumn(groups, k, col, out, spread = 0) {
598
+ function placeColumn(groups, at, out) {
599
+ const { k, col, spread = 0 } = at;
570
600
  let h = 0;
571
601
  for (const g of groups) {
572
- h += placeGroup(g, k, col, col.y + h, out) + spread;
602
+ h += placeGroup(g, { k, col, y0: col.y + h }, out) + spread;
573
603
  }
574
604
  return h - spread;
575
605
  }
@@ -596,9 +626,9 @@ function keepTogether(groups, heights, cut) {
596
626
  return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
597
627
  }
598
628
  function splitColumns(groups, zone) {
599
- const gap = Math.round(zone.w * 0.04);
629
+ const gap = Math.round(zone.w * COLUMN_GAP);
600
630
  const w = (zone.w - gap) / 2;
601
- const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
631
+ const heights = groups.map((g) => placeGroup(g, { k: 1, col: { ...zone, w }, y0: 0 }, null));
602
632
  const total = columnSum(heights, 0, heights.length);
603
633
  let cut = 1;
604
634
  for (let c = 2; c < groups.length; c++) {
@@ -613,7 +643,7 @@ function splitColumns(groups, zone) {
613
643
  ];
614
644
  }
615
645
  function largestK(columns, zoneH) {
616
- const fits = (k) => columns.every((c) => placeColumn(c.groups, k, c.col, null) <= zoneH);
646
+ const fits = (k) => columns.every((c) => placeColumn(c.groups, { k, col: c.col }, null) <= zoneH);
617
647
  let lo = MIN_K;
618
648
  let hi = 1;
619
649
  if (fits(1)) {
@@ -650,9 +680,9 @@ function flow(ctx, groups, zone, fonts) {
650
680
  }));
651
681
  const { columns, k: lo } = bestColumns(measured, zone);
652
682
  const out = emptyFlow();
653
- const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
683
+ const heights = columns.map((c) => placeColumn(c.groups, { k: lo, col: c.col }, null));
654
684
  const end = Math.max(...heights);
655
- 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));
685
+ columns.forEach((c, i) => placeColumn(c.groups, { k: lo, col: c.col, spread: c.groups.length > 1 && columns.length > 1 ? (end - (heights[i] ?? end)) / (c.groups.length - 1) : 0 }, out));
656
686
  const lines = groups.flatMap((g) => g.lines);
657
687
  out.texts.forEach((t, i) => {
658
688
  const line = lines[i];
@@ -689,11 +719,11 @@ function measuredEnd(f, fallback) {
689
719
  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);
690
720
  return bottoms.length > 0 ? Math.max(...bottoms) : fallback;
691
721
  }
692
- function pictureRegion(layout, geo, body, u, frame) {
722
+ function pictureRegion(layout, geo, body, frame) {
693
723
  if (layout !== "picture-bottom" /* PictureBottom */) {
694
724
  return geo.picture;
695
725
  }
696
- const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u));
726
+ const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * frame.u));
697
727
  return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
698
728
  }
699
729
 
@@ -880,7 +910,7 @@ function render(input, assets, canvas) {
880
910
  const radius = Math.min(width, height) * CARD_RADIUS;
881
911
  [head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
882
912
  const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
883
- const logo = logoImage ? placeContain(logoImage, geo.logo ? zones.logo : capAligned(zones.logo, head), "top" /* Top */, 1, 1) : null;
913
+ const logo = logoImage ? placeContain(logoImage, geo.logo ? zones.logo : capAligned(zones.logo, head), { anchor: "top" /* Top */, alignX: 1, scale: 1 }) : null;
884
914
  if (logoImage && logo) {
885
915
  ctx.drawImage(logoImage, logo.x, logo.y, logo.w, logo.h);
886
916
  }
@@ -889,8 +919,8 @@ function render(input, assets, canvas) {
889
919
  if (backdrop) {
890
920
  drawArch(ctx, backdrop, palette.arch);
891
921
  }
892
- const region = pictureRegion(doc.layout, geo, body, u, { height });
893
- const picture = image && doc.picture ? placeContain(image, region, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
922
+ const region = pictureRegion(doc.layout, geo, body, { height, u });
923
+ const picture = image && doc.picture ? placeContain(image, region, { anchor: doc.picture.anchor, alignX: geo.alignX, scale: doc.picture.scale }) : null;
894
924
  if (image && picture) {
895
925
  ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
896
926
  }