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