@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 +12 -0
- package/dist/index.js +76 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +76 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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,
|
|
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
|
|
413
|
-
var
|
|
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
|
|
445
|
-
const
|
|
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 -
|
|
448
|
-
body: { x: m, y: top, w: w - 2 * m, h: pic -
|
|
449
|
-
picture: { x:
|
|
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.
|
|
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 ?
|
|
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
|
|
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:
|
|
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)),
|