@dloizides/poster-render 0.2.3 → 0.2.4
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 +6 -0
- package/dist/index.js +170 -103
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +170 -103
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -291,73 +291,6 @@ function missingAssets(doc, assets) {
|
|
|
291
291
|
return errors;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
// src/layout/blockGroups.ts
|
|
295
|
-
var BOLD = 700;
|
|
296
|
-
var SEMI = 600;
|
|
297
|
-
var MEDIUM = 500;
|
|
298
|
-
var SCRIPT = 400;
|
|
299
|
-
var makers = (p, u) => ({
|
|
300
|
-
display: (text2, size) => ({ text: text2, role: "display", weight: BOLD, size: size * u, color: p.title }),
|
|
301
|
-
venue: (text2, size) => ({ text: text2, role: "display", weight: MEDIUM, size: size * u, color: p.ink }),
|
|
302
|
-
script: (text2, size) => ({ text: text2, role: "script", weight: SCRIPT, size: size * u, color: p.accent }),
|
|
303
|
-
strong: (text2, size) => ({ text: text2, role: "body", weight: BOLD, size: size * u, color: p.ink }),
|
|
304
|
-
accent: (text2, size) => ({ text: text2, role: "body", weight: SEMI, size: size * u, color: p.accent }),
|
|
305
|
-
item: (text2, size) => ({ text: text2, role: "body", weight: SEMI, size: size * u, color: p.ink, bullet: p.accent }),
|
|
306
|
-
soft: (text2, size) => ({ text: text2, role: "body", weight: MEDIUM, size: size * u, color: p.soft })
|
|
307
|
-
});
|
|
308
|
-
function headerGroups(doc, p, u) {
|
|
309
|
-
const l = makers(p, u);
|
|
310
|
-
const b = doc.blocks;
|
|
311
|
-
const lines = [l.display?.(b.title, 250), b.script ? l.script?.(b.script, 120) : void 0, b.titleBottom ? l.display?.(b.titleBottom, 250) : void 0];
|
|
312
|
-
return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
|
|
313
|
-
}
|
|
314
|
-
function itemGroup(item, l, u) {
|
|
315
|
-
const lines = [l.script?.(item.date, 67)];
|
|
316
|
-
item.schedule.forEach((row) => lines.push(l.strong?.(`${row.label} ${row.time}`, 42)));
|
|
317
|
-
if (item.price) {
|
|
318
|
-
lines.push(l.accent?.(item.price, 42));
|
|
319
|
-
}
|
|
320
|
-
return { lines, gapAfter: 48 * u };
|
|
321
|
-
}
|
|
322
|
-
function eventGroups(doc, l, u, withPrices) {
|
|
323
|
-
const b = doc.blocks;
|
|
324
|
-
const groups = [];
|
|
325
|
-
b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
|
|
326
|
-
const prices = (withPrices ? b.prices : void 0) ?? [];
|
|
327
|
-
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 }));
|
|
328
|
-
return groups;
|
|
329
|
-
}
|
|
330
|
-
function listGroups(doc, l, u) {
|
|
331
|
-
const groups = (doc.items ?? []).map((item) => itemGroup(item, l, u));
|
|
332
|
-
const prices = doc.blocks.prices?.map((row) => l.strong?.(`${row.amount} ${row.label}`, 58)) ?? [];
|
|
333
|
-
if (prices.length > 0) {
|
|
334
|
-
groups.push({ lines: prices, gapAfter: 30 * u });
|
|
335
|
-
}
|
|
336
|
-
return groups;
|
|
337
|
-
}
|
|
338
|
-
function flankPriceGroups(doc, p, u) {
|
|
339
|
-
const l = makers(p, u);
|
|
340
|
-
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong?.(row.amount, 110), l.accent?.(row.label, 48)], gapAfter: 30 * u }));
|
|
341
|
-
}
|
|
342
|
-
function bodyGroups(doc, p, u, withPrices = true) {
|
|
343
|
-
const l = makers(p, u);
|
|
344
|
-
const b = doc.blocks;
|
|
345
|
-
const groups = [];
|
|
346
|
-
const isList = doc.layout === "list" /* List */;
|
|
347
|
-
if (b.venue) {
|
|
348
|
-
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 } });
|
|
349
|
-
}
|
|
350
|
-
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
|
|
351
|
-
if (b.includes) {
|
|
352
|
-
const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
|
|
353
|
-
groups.push({ lines, gapAfter: 24 * u, uniform: true });
|
|
354
|
-
}
|
|
355
|
-
if (b.footer) {
|
|
356
|
-
groups.push({ lines: [l.soft?.(b.footer, 38)], gapAfter: 0 });
|
|
357
|
-
}
|
|
358
|
-
return groups;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
294
|
// src/layout/layoutSpecs.ts
|
|
362
295
|
var flanksBeside = (pic, top, bottom2, m) => [
|
|
363
296
|
[m, top, pic[0] - 2 * m, bottom2 - top],
|
|
@@ -409,7 +342,7 @@ var HEADER_PORTRAIT = 0.3;
|
|
|
409
342
|
var HEADER_LANDSCAPE = 0.36;
|
|
410
343
|
var BOTTOM_PICTURE_W = 0.8;
|
|
411
344
|
var BOTTOM_MIN_TOP = 0.25;
|
|
412
|
-
var BOTTOM_HEADER_SHARE = 0.
|
|
345
|
+
var BOTTOM_HEADER_SHARE = 0.3;
|
|
413
346
|
var BOTTOM_GAP = 40 / 1080;
|
|
414
347
|
var BOTTOM_ASPECT = 0.57;
|
|
415
348
|
var PICTURE_SIDE = {
|
|
@@ -440,11 +373,11 @@ function side(w, h, fraction) {
|
|
|
440
373
|
alignX: 1
|
|
441
374
|
};
|
|
442
375
|
}
|
|
443
|
-
function bottom(w, h) {
|
|
376
|
+
function bottom(w, h, share) {
|
|
444
377
|
const m = r2(Math.min(w, h) * MARGIN);
|
|
445
378
|
const gap = r2(Math.min(w, h) * BOTTOM_GAP);
|
|
446
|
-
const picW = r2(w *
|
|
447
|
-
const picH = Math.min(r2(picW / BOTTOM_ASPECT), h - r2(h * BOTTOM_MIN_TOP));
|
|
379
|
+
const picW = r2(w * share);
|
|
380
|
+
const picH = Math.min(r2(picW / BOTTOM_ASPECT), r2((h - r2(h * BOTTOM_MIN_TOP)) * share / BOTTOM_PICTURE_W));
|
|
448
381
|
const pic = h - picH;
|
|
449
382
|
const top = m + r2((pic - m) * BOTTOM_HEADER_SHARE);
|
|
450
383
|
return {
|
|
@@ -466,13 +399,13 @@ function fromSpec(spec, w, h) {
|
|
|
466
399
|
};
|
|
467
400
|
}
|
|
468
401
|
var mirror = (rect, w) => ({ ...rect, x: w - rect.x - rect.w });
|
|
469
|
-
function geometry(layout, w, h) {
|
|
402
|
+
function geometry(layout, w, h, give = 0) {
|
|
470
403
|
const oriented = LAYOUT_SPECS[layout];
|
|
471
404
|
if (oriented) {
|
|
472
405
|
return fromSpec(h >= w ? oriented.portrait : oriented.landscape, w, h);
|
|
473
406
|
}
|
|
474
407
|
if (layout === "picture-bottom" /* PictureBottom */) {
|
|
475
|
-
return bottom(w, h);
|
|
408
|
+
return bottom(w, h, BOTTOM_PICTURE_W - give);
|
|
476
409
|
}
|
|
477
410
|
const g = side(w, h, PICTURE_SIDE[layout] ?? [0.6, 0.42]);
|
|
478
411
|
if (layout !== "picture-left" /* PictureLeft */) {
|
|
@@ -492,6 +425,73 @@ function placeContain(image, region, anchor, alignX, scale) {
|
|
|
492
425
|
};
|
|
493
426
|
}
|
|
494
427
|
|
|
428
|
+
// src/layout/blockGroups.ts
|
|
429
|
+
var BOLD = 700;
|
|
430
|
+
var SEMI = 600;
|
|
431
|
+
var MEDIUM = 500;
|
|
432
|
+
var SCRIPT = 400;
|
|
433
|
+
var makers = (p, u) => ({
|
|
434
|
+
display: (text2, size) => ({ text: text2, role: "display", weight: BOLD, size: size * u, color: p.title }),
|
|
435
|
+
venue: (text2, size) => ({ text: text2, role: "display", weight: MEDIUM, size: size * u, color: p.ink }),
|
|
436
|
+
script: (text2, size) => ({ text: text2, role: "script", weight: SCRIPT, size: size * u, color: p.accent }),
|
|
437
|
+
strong: (text2, size) => ({ text: text2, role: "body", weight: BOLD, size: size * u, color: p.ink }),
|
|
438
|
+
accent: (text2, size) => ({ text: text2, role: "body", weight: SEMI, size: size * u, color: p.accent }),
|
|
439
|
+
item: (text2, size) => ({ text: text2, role: "body", weight: SEMI, size: size * u, color: p.ink, bullet: p.accent }),
|
|
440
|
+
soft: (text2, size) => ({ text: text2, role: "body", weight: MEDIUM, size: size * u, color: p.soft })
|
|
441
|
+
});
|
|
442
|
+
function headerGroups(doc, p, u) {
|
|
443
|
+
const l = makers(p, u);
|
|
444
|
+
const b = doc.blocks;
|
|
445
|
+
const lines = [l.display?.(b.title, 250), b.script ? l.script?.(b.script, 120) : void 0, b.titleBottom ? l.display?.(b.titleBottom, 250) : void 0];
|
|
446
|
+
return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
|
|
447
|
+
}
|
|
448
|
+
function itemGroup(item, l, u) {
|
|
449
|
+
const lines = [l.script?.(item.date, 67)];
|
|
450
|
+
item.schedule.forEach((row) => lines.push(l.strong?.(`${row.label} ${row.time}`, 42)));
|
|
451
|
+
if (item.price) {
|
|
452
|
+
lines.push(l.accent?.(item.price, 42));
|
|
453
|
+
}
|
|
454
|
+
return { lines, gapAfter: 48 * u };
|
|
455
|
+
}
|
|
456
|
+
function eventGroups(doc, l, u, withPrices) {
|
|
457
|
+
const b = doc.blocks;
|
|
458
|
+
const groups = [];
|
|
459
|
+
b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
|
|
460
|
+
const prices = (withPrices ? b.prices : void 0) ?? [];
|
|
461
|
+
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 }));
|
|
462
|
+
return groups;
|
|
463
|
+
}
|
|
464
|
+
function listGroups(doc, l, u) {
|
|
465
|
+
const groups = (doc.items ?? []).map((item) => itemGroup(item, l, u));
|
|
466
|
+
const prices = doc.blocks.prices?.map((row) => l.strong?.(`${row.amount} ${row.label}`, 58)) ?? [];
|
|
467
|
+
if (prices.length > 0) {
|
|
468
|
+
groups.push({ lines: prices, gapAfter: 30 * u });
|
|
469
|
+
}
|
|
470
|
+
return groups;
|
|
471
|
+
}
|
|
472
|
+
function flankPriceGroups(doc, p, u) {
|
|
473
|
+
const l = makers(p, u);
|
|
474
|
+
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong?.(row.amount, 110), l.accent?.(row.label, 48)], gapAfter: 30 * u }));
|
|
475
|
+
}
|
|
476
|
+
function bodyGroups(doc, p, u, withPrices = true) {
|
|
477
|
+
const l = makers(p, u);
|
|
478
|
+
const b = doc.blocks;
|
|
479
|
+
const groups = [];
|
|
480
|
+
const isList = doc.layout === "list" /* List */;
|
|
481
|
+
if (b.venue) {
|
|
482
|
+
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 } });
|
|
483
|
+
}
|
|
484
|
+
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
|
|
485
|
+
if (b.includes) {
|
|
486
|
+
const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
|
|
487
|
+
groups.push({ lines, gapAfter: 24 * u, uniform: true });
|
|
488
|
+
}
|
|
489
|
+
if (b.footer) {
|
|
490
|
+
groups.push({ lines: [l.soft?.(b.footer, 38)], gapAfter: 0 });
|
|
491
|
+
}
|
|
492
|
+
return groups;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
495
|
// src/layout/textFlow.ts
|
|
496
496
|
var LINE_HEIGHT = 1.2;
|
|
497
497
|
var BULLET_INDENT = 1.1;
|
|
@@ -499,6 +499,8 @@ var BULLET_RADIUS = 0.2;
|
|
|
499
499
|
var RULE_GAP = 2.5;
|
|
500
500
|
var RULE_WIDTH = 0.9;
|
|
501
501
|
var WIDE = 1.6;
|
|
502
|
+
var TWO_COLUMN_MIN = 0.75;
|
|
503
|
+
var TWO_COLUMN_GAIN = 1.15;
|
|
502
504
|
var MIN_K = 0.02;
|
|
503
505
|
var STEPS = 22;
|
|
504
506
|
var ICON_SCALE = 1.6;
|
|
@@ -594,9 +596,6 @@ function keepTogether(groups, heights, cut) {
|
|
|
594
596
|
return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
|
|
595
597
|
}
|
|
596
598
|
function splitColumns(groups, zone) {
|
|
597
|
-
if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
|
|
598
|
-
return [{ col: zone, groups }];
|
|
599
|
-
}
|
|
600
599
|
const gap = Math.round(zone.w * 0.04);
|
|
601
600
|
const w = (zone.w - gap) / 2;
|
|
602
601
|
const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
|
|
@@ -613,16 +612,8 @@ function splitColumns(groups, zone) {
|
|
|
613
612
|
{ col: { ...zone, x: zone.x + w + gap, w }, groups: groups.slice(cut) }
|
|
614
613
|
];
|
|
615
614
|
}
|
|
616
|
-
function
|
|
617
|
-
const
|
|
618
|
-
group,
|
|
619
|
-
widths: group.lines.map((line) => {
|
|
620
|
-
ctx.font = fontOf(line, line.size, fonts);
|
|
621
|
-
return ctx.measureText(line.text).width;
|
|
622
|
-
})
|
|
623
|
-
}));
|
|
624
|
-
const columns = splitColumns(measured, zone);
|
|
625
|
-
const fits = (k) => columns.every((c) => placeColumn(c.groups, k, c.col, null) <= zone.h);
|
|
615
|
+
function largestK(columns, zoneH) {
|
|
616
|
+
const fits = (k) => columns.every((c) => placeColumn(c.groups, k, c.col, null) <= zoneH);
|
|
626
617
|
let lo = MIN_K;
|
|
627
618
|
let hi = 1;
|
|
628
619
|
if (fits(1)) {
|
|
@@ -636,6 +627,28 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
636
627
|
hi = mid;
|
|
637
628
|
}
|
|
638
629
|
}
|
|
630
|
+
return lo;
|
|
631
|
+
}
|
|
632
|
+
function bestColumns(measured, zone) {
|
|
633
|
+
const single = [{ col: zone, groups: measured }];
|
|
634
|
+
const k1 = largestK(single, zone.h);
|
|
635
|
+
if (measured.length < 2 || zone.w / Math.max(zone.h, 1) < TWO_COLUMN_MIN) {
|
|
636
|
+
return { columns: single, k: k1 };
|
|
637
|
+
}
|
|
638
|
+
const split = splitColumns(measured, zone);
|
|
639
|
+
const k2 = largestK(split, zone.h);
|
|
640
|
+
const forced = zone.w / Math.max(zone.h, 1) > WIDE;
|
|
641
|
+
return forced || k2 > k1 * TWO_COLUMN_GAIN ? { columns: split, k: k2 } : { columns: single, k: k1 };
|
|
642
|
+
}
|
|
643
|
+
function flow(ctx, groups, zone, fonts) {
|
|
644
|
+
const measured = groups.map((group) => ({
|
|
645
|
+
group,
|
|
646
|
+
widths: group.lines.map((line) => {
|
|
647
|
+
ctx.font = fontOf(line, line.size, fonts);
|
|
648
|
+
return ctx.measureText(line.text).width;
|
|
649
|
+
})
|
|
650
|
+
}));
|
|
651
|
+
const { columns, k: lo } = bestColumns(measured, zone);
|
|
639
652
|
const out = emptyFlow();
|
|
640
653
|
const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
|
|
641
654
|
const end = Math.max(...heights);
|
|
@@ -657,19 +670,19 @@ function flowFlanks(ctx, groups, flanks, fonts) {
|
|
|
657
670
|
}
|
|
658
671
|
function smallestPerLine(groups, results) {
|
|
659
672
|
const sizes = results.flatMap((r3) => r3.texts.map((t) => t.box.h / LINE_HEIGHT2));
|
|
660
|
-
const
|
|
673
|
+
const smallest2 = [];
|
|
661
674
|
let i = 0;
|
|
662
675
|
groups.forEach(
|
|
663
676
|
(g) => g.lines.forEach((_line, j) => {
|
|
664
|
-
|
|
677
|
+
smallest2[j] = Math.min(smallest2[j] ?? Infinity, sizes[i] ?? Infinity);
|
|
665
678
|
i++;
|
|
666
679
|
})
|
|
667
680
|
);
|
|
668
|
-
return
|
|
681
|
+
return smallest2;
|
|
669
682
|
}
|
|
670
683
|
function equalFlanks(ctx, groups, flanks, fonts) {
|
|
671
|
-
const
|
|
672
|
-
const sized = groups.map((g) => ({ ...g, lines: g.lines.map((line, j) => ({ ...line, size: Math.min(line.size,
|
|
684
|
+
const smallest2 = smallestPerLine(groups, flowFlanks(ctx, groups, flanks, fonts));
|
|
685
|
+
const sized = groups.map((g) => ({ ...g, lines: g.lines.map((line, j) => ({ ...line, size: Math.min(line.size, smallest2[j] ?? line.size) })) }));
|
|
673
686
|
return flowFlanks(ctx, sized, flanks, fonts);
|
|
674
687
|
}
|
|
675
688
|
function measuredEnd(f, fallback) {
|
|
@@ -684,9 +697,72 @@ function pictureRegion(layout, geo, body, u, frame) {
|
|
|
684
697
|
return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
|
|
685
698
|
}
|
|
686
699
|
|
|
687
|
-
// src/render/
|
|
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
|
+
// src/render/fitToFloor.ts
|
|
730
|
+
var FLOOR_PX = 30;
|
|
688
731
|
var BASE = 1080;
|
|
732
|
+
var LINE_HEIGHT3 = 1.2;
|
|
689
733
|
var LOGO_SHARE = 0.26;
|
|
734
|
+
var GIVE_STEP = 0.025;
|
|
735
|
+
var GIVE_STEPS = 32;
|
|
736
|
+
function splitHeader(header, hasLogo) {
|
|
737
|
+
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
738
|
+
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
739
|
+
}
|
|
740
|
+
function flowAll(ctx, input, geo, u) {
|
|
741
|
+
const { doc, palette, fonts } = input;
|
|
742
|
+
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
743
|
+
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
744
|
+
const flanks = geo.flanks ?? [];
|
|
745
|
+
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
746
|
+
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
747
|
+
return { geo, zones, head, body, sides };
|
|
748
|
+
}
|
|
749
|
+
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 geometryWithGive(layout, width, height, give) {
|
|
751
|
+
return layout === "picture-bottom" /* PictureBottom */ ? geometry(layout, width, height, give) : yieldToText(geometry(layout, width, height), give);
|
|
752
|
+
}
|
|
753
|
+
function fitToFloor(ctx, input) {
|
|
754
|
+
const { doc, width, height } = input;
|
|
755
|
+
const u = Math.min(width, height) / BASE;
|
|
756
|
+
const floor = FLOOR_PX * u;
|
|
757
|
+
let fitted = flowAll(ctx, input, geometryWithGive(doc.layout, width, height, 0), u);
|
|
758
|
+
for (let step = 1; step <= GIVE_STEPS && smallest(fitted) < floor; step++) {
|
|
759
|
+
fitted = flowAll(ctx, input, geometryWithGive(doc.layout, width, height, step * GIVE_STEP), u);
|
|
760
|
+
}
|
|
761
|
+
return fitted;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/render/render.ts
|
|
765
|
+
var BASE2 = 1080;
|
|
690
766
|
var CARD_RADIUS = 0.035;
|
|
691
767
|
var CAP_TOP = 0.15;
|
|
692
768
|
var PIN_HEAD_Y = 0.08;
|
|
@@ -781,10 +857,6 @@ function capAligned(zone, head) {
|
|
|
781
857
|
const shift = first ? Math.min(Math.max(0, Math.round(first.y + first.h * CAP_TOP) - zone.y), Math.round(zone.h / 2)) : 0;
|
|
782
858
|
return { ...zone, y: zone.y + shift, h: zone.h - shift };
|
|
783
859
|
}
|
|
784
|
-
function splitHeader(header, hasLogo) {
|
|
785
|
-
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
786
|
-
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
787
|
-
}
|
|
788
860
|
function render(input, assets, canvas) {
|
|
789
861
|
const doc = checked(input, assets);
|
|
790
862
|
const { width, height } = FORMAT_SIZES[doc.format];
|
|
@@ -796,8 +868,8 @@ function render(input, assets, canvas) {
|
|
|
796
868
|
}
|
|
797
869
|
const palette = PALETTES[doc.palette];
|
|
798
870
|
const fonts = FONT_SETS[doc.fonts];
|
|
799
|
-
const
|
|
800
|
-
const
|
|
871
|
+
const u = Math.min(width, height) / BASE2;
|
|
872
|
+
const { geo, zones, head, body, sides } = fitToFloor(ctx, { doc, palette, fonts, width, height });
|
|
801
873
|
ctx.globalAlpha = 1;
|
|
802
874
|
ctx.fillStyle = palette.background;
|
|
803
875
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -805,11 +877,6 @@ function render(input, assets, canvas) {
|
|
|
805
877
|
ctx.fillStyle = palette.tint;
|
|
806
878
|
[geo.header, geo.body].forEach((r3) => ctx.fillRect(r3.x, r3.y, r3.w, r3.h));
|
|
807
879
|
}
|
|
808
|
-
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
809
|
-
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
810
|
-
const flanks = geo.flanks ?? [];
|
|
811
|
-
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
812
|
-
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
813
880
|
const radius = Math.min(width, height) * CARD_RADIUS;
|
|
814
881
|
[head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
|
|
815
882
|
const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
|