@dloizides/poster-render 0.2.4 → 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/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 +183 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -86
- 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,11 +345,15 @@ 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
|
};
|
|
354
|
+
var SHORT_PORTRAIT = 1.5;
|
|
355
|
+
var SHORT_PICTURE_W = 0.5;
|
|
356
|
+
var SHORT_PICTURE_H = 0.75;
|
|
353
357
|
var ANCHOR_Y = { ["top" /* Top */]: 0, ["center" /* Center */]: 0.5, ["bottom" /* Bottom */]: 1 };
|
|
354
358
|
var r2 = Math.round;
|
|
355
359
|
function side(w, h, fraction) {
|
|
@@ -373,6 +377,20 @@ function side(w, h, fraction) {
|
|
|
373
377
|
alignX: 1
|
|
374
378
|
};
|
|
375
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
|
+
}
|
|
376
394
|
function bottom(w, h, share) {
|
|
377
395
|
const m = r2(Math.min(w, h) * MARGIN);
|
|
378
396
|
const gap = r2(Math.min(w, h) * BOTTOM_GAP);
|
|
@@ -407,13 +425,15 @@ function geometry(layout, w, h, give = 0) {
|
|
|
407
425
|
if (layout === "picture-bottom" /* PictureBottom */) {
|
|
408
426
|
return bottom(w, h, BOTTOM_PICTURE_W - give);
|
|
409
427
|
}
|
|
410
|
-
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);
|
|
411
430
|
if (layout !== "picture-left" /* PictureLeft */) {
|
|
412
431
|
return g;
|
|
413
432
|
}
|
|
414
|
-
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 };
|
|
415
434
|
}
|
|
416
|
-
function placeContain(image, region,
|
|
435
|
+
function placeContain(image, region, fit) {
|
|
436
|
+
const { anchor, alignX, scale } = fit;
|
|
417
437
|
const s = Math.min(region.w / image.width, region.h / image.height) * scale;
|
|
418
438
|
const w = Math.floor(image.width * s);
|
|
419
439
|
const h = Math.floor(image.height * s);
|
|
@@ -430,6 +450,29 @@ var BOLD = 700;
|
|
|
430
450
|
var SEMI = 600;
|
|
431
451
|
var MEDIUM = 500;
|
|
432
452
|
var SCRIPT = 400;
|
|
453
|
+
var SIZE = {
|
|
454
|
+
title: 250,
|
|
455
|
+
headerScript: 120,
|
|
456
|
+
itemDate: 67,
|
|
457
|
+
itemRow: 42,
|
|
458
|
+
scheduleLabel: 66,
|
|
459
|
+
scheduleTime: 60,
|
|
460
|
+
priceAmount: 66,
|
|
461
|
+
priceLabel: 52,
|
|
462
|
+
listPrice: 58,
|
|
463
|
+
flankAmount: 110,
|
|
464
|
+
flankLabel: 48,
|
|
465
|
+
venue: 58,
|
|
466
|
+
listVenue: 48,
|
|
467
|
+
small: 38
|
|
468
|
+
};
|
|
469
|
+
var GAP = {
|
|
470
|
+
item: 48,
|
|
471
|
+
row: 30,
|
|
472
|
+
venue: 40,
|
|
473
|
+
includes: 24,
|
|
474
|
+
rule: 8
|
|
475
|
+
};
|
|
433
476
|
var makers = (p, u) => ({
|
|
434
477
|
display: (text2, size) => ({ text: text2, role: "display", weight: BOLD, size: size * u, color: p.title }),
|
|
435
478
|
venue: (text2, size) => ({ text: text2, role: "display", weight: MEDIUM, size: size * u, color: p.ink }),
|
|
@@ -442,52 +485,75 @@ var makers = (p, u) => ({
|
|
|
442
485
|
function headerGroups(doc, p, u) {
|
|
443
486
|
const l = makers(p, u);
|
|
444
487
|
const b = doc.blocks;
|
|
445
|
-
const lines = [l.display
|
|
488
|
+
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
489
|
return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
|
|
447
490
|
}
|
|
448
491
|
function itemGroup(item, l, u) {
|
|
449
|
-
const lines = [l.script
|
|
450
|
-
item.schedule.forEach((row) => lines.push(l.strong
|
|
492
|
+
const lines = [l.script(item.date, SIZE.itemDate)];
|
|
493
|
+
item.schedule.forEach((row) => lines.push(l.strong(`${row.label} ${row.time}`, SIZE.itemRow)));
|
|
451
494
|
if (item.price) {
|
|
452
|
-
lines.push(l.accent
|
|
495
|
+
lines.push(l.accent(item.price, SIZE.itemRow));
|
|
496
|
+
}
|
|
497
|
+
return { lines, gapAfter: GAP.item * u };
|
|
498
|
+
}
|
|
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];
|
|
453
505
|
}
|
|
454
|
-
|
|
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)];
|
|
455
508
|
}
|
|
456
|
-
function
|
|
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) {
|
|
457
514
|
const b = doc.blocks;
|
|
458
515
|
const groups = [];
|
|
459
|
-
b.schedule
|
|
460
|
-
|
|
461
|
-
|
|
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
|
+
});
|
|
462
526
|
return groups;
|
|
463
527
|
}
|
|
464
528
|
function listGroups(doc, l, u) {
|
|
465
529
|
const groups = (doc.items ?? []).map((item) => itemGroup(item, l, u));
|
|
466
|
-
const prices = doc.blocks.prices?.map((row) => l.strong
|
|
530
|
+
const prices = doc.blocks.prices?.map((row) => l.strong(`${row.amount} ${row.label}`, SIZE.listPrice)) ?? [];
|
|
467
531
|
if (prices.length > 0) {
|
|
468
|
-
groups.push({ lines: prices, gapAfter:
|
|
532
|
+
groups.push({ lines: prices, gapAfter: GAP.row * u });
|
|
469
533
|
}
|
|
470
534
|
return groups;
|
|
471
535
|
}
|
|
472
536
|
function flankPriceGroups(doc, p, u) {
|
|
473
537
|
const l = makers(p, u);
|
|
474
|
-
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong
|
|
538
|
+
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong(row.amount, SIZE.flankAmount), l.accent(row.label, SIZE.flankLabel)], gapAfter: GAP.row * u }));
|
|
475
539
|
}
|
|
476
|
-
function bodyGroups(doc, p, u,
|
|
540
|
+
function bodyGroups(doc, p, u, options = {}) {
|
|
477
541
|
const l = makers(p, u);
|
|
478
542
|
const b = doc.blocks;
|
|
479
543
|
const groups = [];
|
|
480
544
|
const isList = doc.layout === "list" /* List */;
|
|
481
545
|
if (b.venue) {
|
|
482
|
-
|
|
546
|
+
const size = isList ? SIZE.listVenue : SIZE.venue;
|
|
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 });
|
|
483
549
|
}
|
|
484
|
-
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u,
|
|
550
|
+
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, options));
|
|
485
551
|
if (b.includes) {
|
|
486
|
-
const lines = [l.accent
|
|
487
|
-
groups.push({ lines, gapAfter:
|
|
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));
|
|
553
|
+
groups.push({ lines, gapAfter: GAP.includes * u, uniform: true });
|
|
488
554
|
}
|
|
489
555
|
if (b.footer) {
|
|
490
|
-
groups.push({ lines:
|
|
556
|
+
groups.push({ lines: wrapped(l.soft(b.footer, SIZE.small), options.wrap), gapAfter: 0 });
|
|
491
557
|
}
|
|
492
558
|
return groups;
|
|
493
559
|
}
|
|
@@ -499,6 +565,7 @@ var BULLET_RADIUS = 0.2;
|
|
|
499
565
|
var RULE_GAP = 2.5;
|
|
500
566
|
var RULE_WIDTH = 0.9;
|
|
501
567
|
var WIDE = 1.6;
|
|
568
|
+
var COLUMN_GAP = 0.04;
|
|
502
569
|
var TWO_COLUMN_MIN = 0.75;
|
|
503
570
|
var TWO_COLUMN_GAIN = 1.15;
|
|
504
571
|
var MIN_K = 0.02;
|
|
@@ -507,12 +574,13 @@ var ICON_SCALE = 1.6;
|
|
|
507
574
|
var ICON_GAP = 0.35;
|
|
508
575
|
var fontOf = (line, size, fonts) => `${line.weight} ${size}px ${fonts[line.role]}`;
|
|
509
576
|
var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [], icons: [] });
|
|
577
|
+
var indented = (line) => line.bullet !== void 0 || line.hang === true;
|
|
510
578
|
var iconSize = (group, k) => {
|
|
511
579
|
const first = group.lines[0];
|
|
512
580
|
return group.icon && first ? first.size * k * LINE_HEIGHT * ICON_SCALE : 0;
|
|
513
581
|
};
|
|
514
582
|
function lineSize(line, width, k, avail) {
|
|
515
|
-
const indent = line
|
|
583
|
+
const indent = indented(line) ? line.size * k * BULLET_INDENT : 0;
|
|
516
584
|
return Math.max(0, Math.min(line.size * k, line.size * (avail - indent) / Math.max(width, 1)));
|
|
517
585
|
}
|
|
518
586
|
function groupRatio(m, k, avail) {
|
|
@@ -521,15 +589,17 @@ function groupRatio(m, k, avail) {
|
|
|
521
589
|
}
|
|
522
590
|
return Math.min(...m.group.lines.map((line, i) => lineSize(line, m.widths[i] ?? 1, k, avail) / line.size));
|
|
523
591
|
}
|
|
524
|
-
function placeIcon(group,
|
|
592
|
+
function placeIcon(group, box, out) {
|
|
593
|
+
const { d, x, top, bottom: bottom2 } = box;
|
|
525
594
|
if (!group.icon || d <= 0) {
|
|
526
|
-
return
|
|
595
|
+
return bottom2;
|
|
527
596
|
}
|
|
528
|
-
const textH =
|
|
529
|
-
out?.icons.push({ rect: { x, y:
|
|
530
|
-
return
|
|
597
|
+
const textH = bottom2 - top;
|
|
598
|
+
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 });
|
|
599
|
+
return top + Math.max(textH, d);
|
|
531
600
|
}
|
|
532
|
-
function placeGroup(m,
|
|
601
|
+
function placeGroup(m, at, out) {
|
|
602
|
+
const { k, col, y0 } = at;
|
|
533
603
|
const { group } = m;
|
|
534
604
|
let y = y0;
|
|
535
605
|
if (group.rule) {
|
|
@@ -545,7 +615,7 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
545
615
|
const ratio = groupRatio(m, k, avail);
|
|
546
616
|
group.lines.forEach((line, i) => {
|
|
547
617
|
const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, avail) : line.size * ratio;
|
|
548
|
-
const indent = (line
|
|
618
|
+
const indent = (indented(line) ? line.size * k * BULLET_INDENT : 0) + iconIndent;
|
|
549
619
|
const w = (m.widths[i] ?? 0) * size / line.size;
|
|
550
620
|
const x = col.right ? col.x + col.w - pad - w : col.x + pad + indent;
|
|
551
621
|
out?.texts.push({ text: line.text, font: "", color: line.color, box: { x, y, w, h: size * LINE_HEIGHT } });
|
|
@@ -554,7 +624,7 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
554
624
|
}
|
|
555
625
|
y += size * LINE_HEIGHT;
|
|
556
626
|
});
|
|
557
|
-
y = placeIcon(group, d, col.x + pad, top + pad, y, out);
|
|
627
|
+
y = placeIcon(group, { d, x: col.x + pad, top: top + pad, bottom: y }, out);
|
|
558
628
|
y += pad;
|
|
559
629
|
if (group.rule && group.ruleBelow) {
|
|
560
630
|
y += group.rule.height * k * (RULE_GAP - 1);
|
|
@@ -566,10 +636,11 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
566
636
|
}
|
|
567
637
|
return y + group.gapAfter * k - y0;
|
|
568
638
|
}
|
|
569
|
-
function placeColumn(groups,
|
|
639
|
+
function placeColumn(groups, at, out) {
|
|
640
|
+
const { k, col, spread = 0 } = at;
|
|
570
641
|
let h = 0;
|
|
571
642
|
for (const g of groups) {
|
|
572
|
-
h += placeGroup(g, k, col, col.y + h, out) + spread;
|
|
643
|
+
h += placeGroup(g, { k, col, y0: col.y + h }, out) + spread;
|
|
573
644
|
}
|
|
574
645
|
return h - spread;
|
|
575
646
|
}
|
|
@@ -596,9 +667,9 @@ function keepTogether(groups, heights, cut) {
|
|
|
596
667
|
return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
|
|
597
668
|
}
|
|
598
669
|
function splitColumns(groups, zone) {
|
|
599
|
-
const gap = Math.round(zone.w *
|
|
670
|
+
const gap = Math.round(zone.w * COLUMN_GAP);
|
|
600
671
|
const w = (zone.w - gap) / 2;
|
|
601
|
-
const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
|
|
672
|
+
const heights = groups.map((g) => placeGroup(g, { k: 1, col: { ...zone, w }, y0: 0 }, null));
|
|
602
673
|
const total = columnSum(heights, 0, heights.length);
|
|
603
674
|
let cut = 1;
|
|
604
675
|
for (let c = 2; c < groups.length; c++) {
|
|
@@ -613,7 +684,7 @@ function splitColumns(groups, zone) {
|
|
|
613
684
|
];
|
|
614
685
|
}
|
|
615
686
|
function largestK(columns, zoneH) {
|
|
616
|
-
const fits = (k) => columns.every((c) => placeColumn(c.groups, k, c.col, null) <= zoneH);
|
|
687
|
+
const fits = (k) => columns.every((c) => placeColumn(c.groups, { k, col: c.col }, null) <= zoneH);
|
|
617
688
|
let lo = MIN_K;
|
|
618
689
|
let hi = 1;
|
|
619
690
|
if (fits(1)) {
|
|
@@ -650,9 +721,9 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
650
721
|
}));
|
|
651
722
|
const { columns, k: lo } = bestColumns(measured, zone);
|
|
652
723
|
const out = emptyFlow();
|
|
653
|
-
const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
|
|
724
|
+
const heights = columns.map((c) => placeColumn(c.groups, { k: lo, col: c.col }, null));
|
|
654
725
|
const end = Math.max(...heights);
|
|
655
|
-
columns.forEach((c, i) => placeColumn(c.groups, lo, c.col,
|
|
726
|
+
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
727
|
const lines = groups.flatMap((g) => g.lines);
|
|
657
728
|
out.texts.forEach((t, i) => {
|
|
658
729
|
const line = lines[i];
|
|
@@ -689,43 +760,14 @@ function measuredEnd(f, fallback) {
|
|
|
689
760
|
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
761
|
return bottoms.length > 0 ? Math.max(...bottoms) : fallback;
|
|
691
762
|
}
|
|
692
|
-
function pictureRegion(layout, geo, body,
|
|
763
|
+
function pictureRegion(layout, geo, body, frame) {
|
|
693
764
|
if (layout !== "picture-bottom" /* PictureBottom */) {
|
|
694
765
|
return geo.picture;
|
|
695
766
|
}
|
|
696
|
-
const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u));
|
|
767
|
+
const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * frame.u));
|
|
697
768
|
return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
|
|
698
769
|
}
|
|
699
770
|
|
|
700
|
-
// src/render/yieldToText.ts
|
|
701
|
-
function yieldToText(geo, give) {
|
|
702
|
-
const { picture: p, body: b } = geo;
|
|
703
|
-
if (give <= 0) {
|
|
704
|
-
return geo;
|
|
705
|
-
}
|
|
706
|
-
if (p.x >= b.x + b.w) {
|
|
707
|
-
const d = Math.round(p.w * give);
|
|
708
|
-
return { ...geo, picture: { ...p, x: p.x + d, w: p.w - d }, body: { ...b, w: b.w + d } };
|
|
709
|
-
}
|
|
710
|
-
if (p.x + p.w <= b.x) {
|
|
711
|
-
const d = Math.round(p.w * give);
|
|
712
|
-
return { ...geo, picture: { ...p, w: p.w - d }, body: { ...b, x: b.x - d, w: b.w + d } };
|
|
713
|
-
}
|
|
714
|
-
if (p.y >= b.y + b.h) {
|
|
715
|
-
const d = Math.round(p.h * give);
|
|
716
|
-
const stacked = geo.header.y + geo.header.h <= b.y;
|
|
717
|
-
const toHeader = stacked ? Math.round(d / 2) : 0;
|
|
718
|
-
return {
|
|
719
|
-
...geo,
|
|
720
|
-
header: { ...geo.header, h: geo.header.h + toHeader },
|
|
721
|
-
picture: { ...p, y: p.y + d, h: p.h - d },
|
|
722
|
-
body: { ...b, y: b.y + toHeader, h: b.h + d - toHeader },
|
|
723
|
-
flanks: geo.flanks?.map((f) => ({ ...f, y: f.y + d, h: f.h - d }))
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
return geo;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
771
|
// src/render/fitToFloor.ts
|
|
730
772
|
var FLOOR_PX = 30;
|
|
731
773
|
var BASE = 1080;
|
|
@@ -733,33 +775,88 @@ var LINE_HEIGHT3 = 1.2;
|
|
|
733
775
|
var LOGO_SHARE = 0.26;
|
|
734
776
|
var GIVE_STEP = 0.025;
|
|
735
777
|
var GIVE_STEPS = 32;
|
|
778
|
+
var SHIFT_STEP = 0.06;
|
|
779
|
+
var SHIFT_STEPS = 8;
|
|
780
|
+
var MARGIN2 = 0.045;
|
|
736
781
|
function splitHeader(header, hasLogo) {
|
|
737
782
|
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
738
783
|
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
739
784
|
}
|
|
740
|
-
function flowAll(ctx, input, geo,
|
|
785
|
+
function flowAll(ctx, input, geo, reflow) {
|
|
741
786
|
const { doc, palette, fonts } = input;
|
|
787
|
+
const u = Math.min(input.width, input.height) / BASE;
|
|
742
788
|
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
743
789
|
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
744
790
|
const flanks = geo.flanks ?? [];
|
|
745
|
-
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);
|
|
746
792
|
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
747
793
|
return { geo, zones, head, body, sides };
|
|
748
794
|
}
|
|
749
795
|
var smallest = (t) => Math.min(Infinity, ...[t.head, t.body, ...t.sides].flatMap((f) => f.texts.map((x) => x.box.h / LINE_HEIGHT3)));
|
|
750
|
-
function
|
|
751
|
-
|
|
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 } };
|
|
752
804
|
}
|
|
753
|
-
function
|
|
754
|
-
const {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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);
|
|
813
|
+
for (let step = 1; step <= GIVE_STEPS && smallest(fitted) < floor; step++) {
|
|
814
|
+
fitted = flowAll(ctx, input, geometry("picture-bottom" /* PictureBottom */, width, height, step * GIVE_STEP), none);
|
|
815
|
+
}
|
|
816
|
+
return fitted;
|
|
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);
|
|
758
826
|
for (let step = 1; step <= GIVE_STEPS && smallest(fitted) < floor; step++) {
|
|
759
|
-
fitted = flowAll(ctx, input,
|
|
827
|
+
fitted = flowAll(ctx, input, yieldPicture(base2, step * GIVE_STEP), last);
|
|
760
828
|
}
|
|
761
829
|
return fitted;
|
|
762
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
|
+
}
|
|
763
860
|
|
|
764
861
|
// src/render/render.ts
|
|
765
862
|
var BASE2 = 1080;
|
|
@@ -880,7 +977,7 @@ function render(input, assets, canvas) {
|
|
|
880
977
|
const radius = Math.min(width, height) * CARD_RADIUS;
|
|
881
978
|
[head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
|
|
882
979
|
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;
|
|
980
|
+
const logo = logoImage ? placeContain(logoImage, geo.logo ? zones.logo : capAligned(zones.logo, head), { anchor: "top" /* Top */, alignX: 1, scale: 1 }) : null;
|
|
884
981
|
if (logoImage && logo) {
|
|
885
982
|
ctx.drawImage(logoImage, logo.x, logo.y, logo.w, logo.h);
|
|
886
983
|
}
|
|
@@ -889,8 +986,8 @@ function render(input, assets, canvas) {
|
|
|
889
986
|
if (backdrop) {
|
|
890
987
|
drawArch(ctx, backdrop, palette.arch);
|
|
891
988
|
}
|
|
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;
|
|
989
|
+
const region = pictureRegion(doc.layout, geo, body, { height, u });
|
|
990
|
+
const picture = image && doc.picture ? placeContain(image, region, { anchor: doc.picture.anchor, alignX: geo.alignX, scale: doc.picture.scale }) : null;
|
|
894
991
|
if (image && picture) {
|
|
895
992
|
ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
|
|
896
993
|
}
|