@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/CHANGELOG.md +7 -0
- package/README.md +40 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +70 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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 */]:
|
|
350
|
-
["picture-left" /* PictureLeft */]:
|
|
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] ??
|
|
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,
|
|
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
|
|
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
|
|
450
|
-
item.schedule.forEach((row) => lines.push(l.strong
|
|
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
|
|
477
|
+
lines.push(l.accent(item.price, SIZE.itemRow));
|
|
453
478
|
}
|
|
454
|
-
return { lines, gapAfter:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
487
|
-
groups.push({ lines, gapAfter:
|
|
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
|
|
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,
|
|
551
|
+
function placeIcon(group, box, out) {
|
|
552
|
+
const { d, x, top, bottom: bottom2 } = box;
|
|
525
553
|
if (!group.icon || d <= 0) {
|
|
526
|
-
return
|
|
554
|
+
return bottom2;
|
|
527
555
|
}
|
|
528
|
-
const textH =
|
|
529
|
-
out?.icons.push({ rect: { x, y:
|
|
530
|
-
return
|
|
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,
|
|
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,
|
|
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 *
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
}
|