@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 +8 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +84 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
package/dist/index.d.ts
CHANGED
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,
|
|
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:
|
|
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)
|
|
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,
|
|
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
ctx.drawImage(logoImage,
|
|
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
|
}
|