@dloizides/poster-render 0.2.5 → 0.2.6
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.js +119 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +119 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -351,6 +351,9 @@ var PICTURE_SIDE = {
|
|
|
351
351
|
["picture-left" /* PictureLeft */]: DEFAULT_SIDE,
|
|
352
352
|
["list" /* List */]: [0.5, 0.36]
|
|
353
353
|
};
|
|
354
|
+
var SHORT_PORTRAIT = 1.5;
|
|
355
|
+
var SHORT_PICTURE_W = 0.5;
|
|
356
|
+
var SHORT_PICTURE_H = 0.75;
|
|
354
357
|
var ANCHOR_Y = { ["top" /* Top */]: 0, ["center" /* Center */]: 0.5, ["bottom" /* Bottom */]: 1 };
|
|
355
358
|
var r2 = Math.round;
|
|
356
359
|
function side(w, h, fraction) {
|
|
@@ -374,6 +377,20 @@ function side(w, h, fraction) {
|
|
|
374
377
|
alignX: 1
|
|
375
378
|
};
|
|
376
379
|
}
|
|
380
|
+
function shortSide(w, h) {
|
|
381
|
+
const m = r2(Math.min(w, h) * MARGIN);
|
|
382
|
+
const picW = r2(w * SHORT_PICTURE_W);
|
|
383
|
+
const picH = r2(h * SHORT_PICTURE_H);
|
|
384
|
+
const top = r2(h * HEADER_PORTRAIT);
|
|
385
|
+
const textW = w - picW - 2 * m;
|
|
386
|
+
return {
|
|
387
|
+
header: { x: m, y: m, w: textW, h: top - 2 * m },
|
|
388
|
+
body: { x: m, y: top, w: textW, h: h - top - m },
|
|
389
|
+
picture: { x: w - picW, y: h - picH, w: picW, h: picH },
|
|
390
|
+
logo: { x: w - picW + m, y: m, w: picW - 2 * m, h: h - picH - 2 * m },
|
|
391
|
+
alignX: 1
|
|
392
|
+
};
|
|
393
|
+
}
|
|
377
394
|
function bottom(w, h, share) {
|
|
378
395
|
const m = r2(Math.min(w, h) * MARGIN);
|
|
379
396
|
const gap = r2(Math.min(w, h) * BOTTOM_GAP);
|
|
@@ -408,11 +425,12 @@ function geometry(layout, w, h, give = 0) {
|
|
|
408
425
|
if (layout === "picture-bottom" /* PictureBottom */) {
|
|
409
426
|
return bottom(w, h, BOTTOM_PICTURE_W - give);
|
|
410
427
|
}
|
|
411
|
-
const
|
|
428
|
+
const isSide = layout === "picture-right" /* PictureRight */ || layout === "picture-left" /* PictureLeft */;
|
|
429
|
+
const g = isSide && h >= w && h < w * SHORT_PORTRAIT ? shortSide(w, h) : side(w, h, PICTURE_SIDE[layout] ?? DEFAULT_SIDE);
|
|
412
430
|
if (layout !== "picture-left" /* PictureLeft */) {
|
|
413
431
|
return g;
|
|
414
432
|
}
|
|
415
|
-
return { header: mirror(g.header, w), body: mirror(g.body, w), picture: mirror(g.picture, w), alignX: 1 - g.alignX };
|
|
433
|
+
return { header: mirror(g.header, w), body: mirror(g.body, w), picture: mirror(g.picture, w), logo: g.logo ? mirror(g.logo, w) : void 0, alignX: 1 - g.alignX };
|
|
416
434
|
}
|
|
417
435
|
function placeContain(image, region, fit) {
|
|
418
436
|
const { anchor, alignX, scale } = fit;
|
|
@@ -478,12 +496,33 @@ function itemGroup(item, l, u) {
|
|
|
478
496
|
}
|
|
479
497
|
return { lines, gapAfter: GAP.item * u };
|
|
480
498
|
}
|
|
481
|
-
|
|
499
|
+
var WRAP_FROM = 14;
|
|
500
|
+
function halves(text2) {
|
|
501
|
+
const mid = text2.length / 2;
|
|
502
|
+
const spaces = [...text2].flatMap((c, i) => c === " " ? [i] : []);
|
|
503
|
+
if (text2.length < WRAP_FROM || spaces.length === 0) {
|
|
504
|
+
return [text2];
|
|
505
|
+
}
|
|
506
|
+
const cut = spaces.reduce((best, i) => Math.abs(i - mid) < Math.abs(best - mid) ? i : best);
|
|
507
|
+
return [text2.slice(0, cut), text2.slice(cut + 1)];
|
|
508
|
+
}
|
|
509
|
+
function wrapped(line, wrap) {
|
|
510
|
+
const parts = wrap ? halves(line.text) : [line.text];
|
|
511
|
+
return parts.map((text2, i) => i === 0 ? { ...line, text: text2 } : { ...line, text: text2, bullet: void 0, hang: line.bullet !== void 0 });
|
|
512
|
+
}
|
|
513
|
+
function eventGroups(doc, l, u, options) {
|
|
482
514
|
const b = doc.blocks;
|
|
483
515
|
const groups = [];
|
|
484
|
-
b.schedule
|
|
485
|
-
|
|
486
|
-
|
|
516
|
+
const schedule2 = b.schedule ?? [];
|
|
517
|
+
schedule2.forEach((row, i) => {
|
|
518
|
+
const lines = options.compact ? [l.strong(`${row.label} ${row.time}`, SIZE.scheduleLabel)] : [l.strong(row.label, SIZE.scheduleLabel), l.accent(row.time, SIZE.scheduleTime)];
|
|
519
|
+
groups.push({ lines, gapAfter: GAP.row * u, keepWithNext: i < schedule2.length - 1 });
|
|
520
|
+
});
|
|
521
|
+
const prices = (options.withPrices === false ? void 0 : b.prices) ?? [];
|
|
522
|
+
prices.forEach((row, i) => {
|
|
523
|
+
const lines = options.compact ? [l.strong(`${row.amount} ${row.label}`, SIZE.priceAmount)] : [l.strong(row.amount, SIZE.priceAmount), l.accent(row.label, SIZE.priceLabel)];
|
|
524
|
+
groups.push({ lines, gapAfter: GAP.row * u, keepWithNext: i < prices.length - 1 });
|
|
525
|
+
});
|
|
487
526
|
return groups;
|
|
488
527
|
}
|
|
489
528
|
function listGroups(doc, l, u) {
|
|
@@ -498,22 +537,23 @@ function flankPriceGroups(doc, p, u) {
|
|
|
498
537
|
const l = makers(p, u);
|
|
499
538
|
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong(row.amount, SIZE.flankAmount), l.accent(row.label, SIZE.flankLabel)], gapAfter: GAP.row * u }));
|
|
500
539
|
}
|
|
501
|
-
function bodyGroups(doc, p, u,
|
|
540
|
+
function bodyGroups(doc, p, u, options = {}) {
|
|
502
541
|
const l = makers(p, u);
|
|
503
542
|
const b = doc.blocks;
|
|
504
543
|
const groups = [];
|
|
505
544
|
const isList = doc.layout === "list" /* List */;
|
|
506
545
|
if (b.venue) {
|
|
507
546
|
const size = isList ? SIZE.listVenue : SIZE.venue;
|
|
508
|
-
|
|
547
|
+
const keepWithNext = !isList && (b.schedule?.length ?? 0) > 0;
|
|
548
|
+
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 }, keepWithNext });
|
|
509
549
|
}
|
|
510
|
-
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u,
|
|
550
|
+
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, options));
|
|
511
551
|
if (b.includes) {
|
|
512
|
-
const lines = [l.accent(b.includes.heading, SIZE.small), ...b.includes.items.map((i) => l.item(i, SIZE.small))];
|
|
552
|
+
const lines = [l.accent(b.includes.heading, SIZE.small), ...b.includes.items.map((i) => l.item(i, SIZE.small))].flatMap((line) => wrapped(line, options.wrap));
|
|
513
553
|
groups.push({ lines, gapAfter: GAP.includes * u, uniform: true });
|
|
514
554
|
}
|
|
515
555
|
if (b.footer) {
|
|
516
|
-
groups.push({ lines:
|
|
556
|
+
groups.push({ lines: wrapped(l.soft(b.footer, SIZE.small), options.wrap), gapAfter: 0 });
|
|
517
557
|
}
|
|
518
558
|
return groups;
|
|
519
559
|
}
|
|
@@ -534,12 +574,13 @@ var ICON_SCALE = 1.6;
|
|
|
534
574
|
var ICON_GAP = 0.35;
|
|
535
575
|
var fontOf = (line, size, fonts) => `${line.weight} ${size}px ${fonts[line.role]}`;
|
|
536
576
|
var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [], icons: [] });
|
|
577
|
+
var indented = (line) => line.bullet !== void 0 || line.hang === true;
|
|
537
578
|
var iconSize = (group, k) => {
|
|
538
579
|
const first = group.lines[0];
|
|
539
580
|
return group.icon && first ? first.size * k * LINE_HEIGHT * ICON_SCALE : 0;
|
|
540
581
|
};
|
|
541
582
|
function lineSize(line, width, k, avail) {
|
|
542
|
-
const indent = line
|
|
583
|
+
const indent = indented(line) ? line.size * k * BULLET_INDENT : 0;
|
|
543
584
|
return Math.max(0, Math.min(line.size * k, line.size * (avail - indent) / Math.max(width, 1)));
|
|
544
585
|
}
|
|
545
586
|
function groupRatio(m, k, avail) {
|
|
@@ -574,7 +615,7 @@ function placeGroup(m, at, out) {
|
|
|
574
615
|
const ratio = groupRatio(m, k, avail);
|
|
575
616
|
group.lines.forEach((line, i) => {
|
|
576
617
|
const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, avail) : line.size * ratio;
|
|
577
|
-
const indent = (line
|
|
618
|
+
const indent = (indented(line) ? line.size * k * BULLET_INDENT : 0) + iconIndent;
|
|
578
619
|
const w = (m.widths[i] ?? 0) * size / line.size;
|
|
579
620
|
const x = col.right ? col.x + col.w - pad - w : col.x + pad + indent;
|
|
580
621
|
out?.texts.push({ text: line.text, font: "", color: line.color, box: { x, y, w, h: size * LINE_HEIGHT } });
|
|
@@ -727,35 +768,6 @@ function pictureRegion(layout, geo, body, frame) {
|
|
|
727
768
|
return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
|
|
728
769
|
}
|
|
729
770
|
|
|
730
|
-
// src/render/yieldToText.ts
|
|
731
|
-
function yieldToText(geo, give) {
|
|
732
|
-
const { picture: p, body: b } = geo;
|
|
733
|
-
if (give <= 0) {
|
|
734
|
-
return geo;
|
|
735
|
-
}
|
|
736
|
-
if (p.x >= b.x + b.w) {
|
|
737
|
-
const d = Math.round(p.w * give);
|
|
738
|
-
return { ...geo, picture: { ...p, x: p.x + d, w: p.w - d }, body: { ...b, w: b.w + d } };
|
|
739
|
-
}
|
|
740
|
-
if (p.x + p.w <= b.x) {
|
|
741
|
-
const d = Math.round(p.w * give);
|
|
742
|
-
return { ...geo, picture: { ...p, w: p.w - d }, body: { ...b, x: b.x - d, w: b.w + d } };
|
|
743
|
-
}
|
|
744
|
-
if (p.y >= b.y + b.h) {
|
|
745
|
-
const d = Math.round(p.h * give);
|
|
746
|
-
const stacked = geo.header.y + geo.header.h <= b.y;
|
|
747
|
-
const toHeader = stacked ? Math.round(d / 2) : 0;
|
|
748
|
-
return {
|
|
749
|
-
...geo,
|
|
750
|
-
header: { ...geo.header, h: geo.header.h + toHeader },
|
|
751
|
-
picture: { ...p, y: p.y + d, h: p.h - d },
|
|
752
|
-
body: { ...b, y: b.y + toHeader, h: b.h + d - toHeader },
|
|
753
|
-
flanks: geo.flanks?.map((f) => ({ ...f, y: f.y + d, h: f.h - d }))
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
return geo;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
771
|
// src/render/fitToFloor.ts
|
|
760
772
|
var FLOOR_PX = 30;
|
|
761
773
|
var BASE = 1080;
|
|
@@ -763,33 +775,88 @@ var LINE_HEIGHT3 = 1.2;
|
|
|
763
775
|
var LOGO_SHARE = 0.26;
|
|
764
776
|
var GIVE_STEP = 0.025;
|
|
765
777
|
var GIVE_STEPS = 32;
|
|
778
|
+
var SHIFT_STEP = 0.06;
|
|
779
|
+
var SHIFT_STEPS = 8;
|
|
780
|
+
var MARGIN2 = 0.045;
|
|
766
781
|
function splitHeader(header, hasLogo) {
|
|
767
782
|
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
768
783
|
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
769
784
|
}
|
|
770
|
-
function flowAll(ctx, input, geo,
|
|
785
|
+
function flowAll(ctx, input, geo, reflow) {
|
|
771
786
|
const { doc, palette, fonts } = input;
|
|
787
|
+
const u = Math.min(input.width, input.height) / BASE;
|
|
772
788
|
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
773
789
|
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
774
790
|
const flanks = geo.flanks ?? [];
|
|
775
|
-
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
791
|
+
const body = flow(ctx, bodyGroups(doc, palette, u, { withPrices: flanks.length === 0, compact: reflow.compact, wrap: reflow.wrap }), geo.body, fonts);
|
|
776
792
|
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
777
793
|
return { geo, zones, head, body, sides };
|
|
778
794
|
}
|
|
779
795
|
var smallest = (t) => Math.min(Infinity, ...[t.head, t.body, ...t.sides].flatMap((f) => f.texts.map((x) => x.box.h / LINE_HEIGHT3)));
|
|
780
|
-
function
|
|
781
|
-
|
|
796
|
+
function shiftHeader(geo, shift) {
|
|
797
|
+
const { header, body } = geo;
|
|
798
|
+
const stacked = header.y + header.h <= body.y && header.x < body.x + body.w && body.x < header.x + header.w;
|
|
799
|
+
const d = Math.round(header.h * shift);
|
|
800
|
+
if (!stacked || d === 0 || body.h - d <= 0) {
|
|
801
|
+
return geo;
|
|
802
|
+
}
|
|
803
|
+
return { ...geo, header: { ...header, h: header.h - d }, body: { ...body, y: body.y - d, h: body.h + d } };
|
|
782
804
|
}
|
|
783
|
-
function
|
|
784
|
-
const {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
805
|
+
function liftHeader(geo, top) {
|
|
806
|
+
const { header } = geo;
|
|
807
|
+
return header.y > top ? { ...geo, header: { ...header, y: top, h: header.h + header.y - top } } : geo;
|
|
808
|
+
}
|
|
809
|
+
function fitBottom(ctx, input, floor) {
|
|
810
|
+
const { width, height } = input;
|
|
811
|
+
const none = { compact: false, wrap: false};
|
|
812
|
+
let fitted = flowAll(ctx, input, geometry("picture-bottom" /* PictureBottom */, width, height), none);
|
|
788
813
|
for (let step = 1; step <= GIVE_STEPS && smallest(fitted) < floor; step++) {
|
|
789
|
-
fitted = flowAll(ctx, input,
|
|
814
|
+
fitted = flowAll(ctx, input, geometry("picture-bottom" /* PictureBottom */, width, height, step * GIVE_STEP), none);
|
|
790
815
|
}
|
|
791
816
|
return fitted;
|
|
792
817
|
}
|
|
818
|
+
function yieldPicture(geo, give) {
|
|
819
|
+
const { picture: p, body: b } = geo;
|
|
820
|
+
const d = Math.round(p.w * give);
|
|
821
|
+
return { ...geo, picture: { ...p, x: p.x + d, w: p.w - d }, body: { ...b, w: b.w + d } };
|
|
822
|
+
}
|
|
823
|
+
function fitList(ctx, input, floor, base2) {
|
|
824
|
+
const last = { compact: true, wrap: true};
|
|
825
|
+
let fitted = flowAll(ctx, input, base2, last);
|
|
826
|
+
for (let step = 1; step <= GIVE_STEPS && smallest(fitted) < floor; step++) {
|
|
827
|
+
fitted = flowAll(ctx, input, yieldPicture(base2, step * GIVE_STEP), last);
|
|
828
|
+
}
|
|
829
|
+
return fitted;
|
|
830
|
+
}
|
|
831
|
+
var SHIFTS = [0, ...Array.from({ length: SHIFT_STEPS }, (_v, i) => [(i + 1) * SHIFT_STEP, -(i + 1) * SHIFT_STEP]).flat()];
|
|
832
|
+
var TIERS = [
|
|
833
|
+
{ compact: false, wrap: false, lift: false },
|
|
834
|
+
{ compact: true, wrap: false, lift: false },
|
|
835
|
+
{ compact: true, wrap: true, lift: false },
|
|
836
|
+
{ compact: true, wrap: true, lift: true }
|
|
837
|
+
];
|
|
838
|
+
var REFLOWS = TIERS.flatMap((tier) => SHIFTS.map((shift) => ({ ...tier, shift })));
|
|
839
|
+
function fitToFloor(ctx, input) {
|
|
840
|
+
const { doc, width, height } = input;
|
|
841
|
+
const floor = FLOOR_PX * Math.min(width, height) / BASE;
|
|
842
|
+
if (doc.layout === "picture-bottom" /* PictureBottom */) {
|
|
843
|
+
return fitBottom(ctx, input, floor);
|
|
844
|
+
}
|
|
845
|
+
const base2 = geometry(doc.layout, width, height);
|
|
846
|
+
const top = Math.round(Math.min(width, height) * MARGIN2);
|
|
847
|
+
let best = null;
|
|
848
|
+
for (const reflow of REFLOWS) {
|
|
849
|
+
const fitted = flowAll(ctx, input, shiftHeader(reflow.lift ? liftHeader(base2, top) : base2, reflow.shift), reflow);
|
|
850
|
+
if (smallest(fitted) >= floor) {
|
|
851
|
+
return fitted;
|
|
852
|
+
}
|
|
853
|
+
best = best === null || smallest(fitted) > smallest(best) ? fitted : best;
|
|
854
|
+
}
|
|
855
|
+
if (doc.layout === "list" /* List */) {
|
|
856
|
+
return fitList(ctx, input, floor, base2);
|
|
857
|
+
}
|
|
858
|
+
return best ?? flowAll(ctx, input, base2, { compact: false, wrap: false});
|
|
859
|
+
}
|
|
793
860
|
|
|
794
861
|
// src/render/render.ts
|
|
795
862
|
var BASE2 = 1080;
|