@dloizides/poster-render 0.2.2 → 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 +10 -0
- package/dist/index.js +183 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -112
- 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],
|
|
@@ -407,8 +340,11 @@ var toRect = (f, w, h) => {
|
|
|
407
340
|
var MARGIN = 0.045;
|
|
408
341
|
var HEADER_PORTRAIT = 0.3;
|
|
409
342
|
var HEADER_LANDSCAPE = 0.36;
|
|
410
|
-
var
|
|
411
|
-
var
|
|
343
|
+
var BOTTOM_PICTURE_W = 0.8;
|
|
344
|
+
var BOTTOM_MIN_TOP = 0.25;
|
|
345
|
+
var BOTTOM_HEADER_SHARE = 0.3;
|
|
346
|
+
var BOTTOM_GAP = 40 / 1080;
|
|
347
|
+
var BOTTOM_ASPECT = 0.57;
|
|
412
348
|
var PICTURE_SIDE = {
|
|
413
349
|
["picture-right" /* PictureRight */]: [0.6, 0.42],
|
|
414
350
|
["picture-left" /* PictureLeft */]: [0.6, 0.42],
|
|
@@ -437,14 +373,17 @@ function side(w, h, fraction) {
|
|
|
437
373
|
alignX: 1
|
|
438
374
|
};
|
|
439
375
|
}
|
|
440
|
-
function bottom(w, h) {
|
|
376
|
+
function bottom(w, h, share) {
|
|
441
377
|
const m = r2(Math.min(w, h) * MARGIN);
|
|
442
|
-
const
|
|
443
|
-
const
|
|
378
|
+
const gap = r2(Math.min(w, h) * BOTTOM_GAP);
|
|
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));
|
|
381
|
+
const pic = h - picH;
|
|
382
|
+
const top = m + r2((pic - m) * BOTTOM_HEADER_SHARE);
|
|
444
383
|
return {
|
|
445
|
-
header: { x: m, y: m, w: w - 2 * m, h: top -
|
|
446
|
-
body: { x: m, y: top, w: w - 2 * m, h: pic -
|
|
447
|
-
picture: { x:
|
|
384
|
+
header: { x: m, y: m, w: w - 2 * m, h: top - m },
|
|
385
|
+
body: { x: m, y: top, w: w - 2 * m, h: pic - gap - top },
|
|
386
|
+
picture: { x: r2((w - picW) / 2), y: pic, w: picW, h: picH },
|
|
448
387
|
alignX: 0.5
|
|
449
388
|
};
|
|
450
389
|
}
|
|
@@ -460,13 +399,13 @@ function fromSpec(spec, w, h) {
|
|
|
460
399
|
};
|
|
461
400
|
}
|
|
462
401
|
var mirror = (rect, w) => ({ ...rect, x: w - rect.x - rect.w });
|
|
463
|
-
function geometry(layout, w, h) {
|
|
402
|
+
function geometry(layout, w, h, give = 0) {
|
|
464
403
|
const oriented = LAYOUT_SPECS[layout];
|
|
465
404
|
if (oriented) {
|
|
466
405
|
return fromSpec(h >= w ? oriented.portrait : oriented.landscape, w, h);
|
|
467
406
|
}
|
|
468
407
|
if (layout === "picture-bottom" /* PictureBottom */) {
|
|
469
|
-
return bottom(w, h);
|
|
408
|
+
return bottom(w, h, BOTTOM_PICTURE_W - give);
|
|
470
409
|
}
|
|
471
410
|
const g = side(w, h, PICTURE_SIDE[layout] ?? [0.6, 0.42]);
|
|
472
411
|
if (layout !== "picture-left" /* PictureLeft */) {
|
|
@@ -486,6 +425,73 @@ function placeContain(image, region, anchor, alignX, scale) {
|
|
|
486
425
|
};
|
|
487
426
|
}
|
|
488
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
|
+
|
|
489
495
|
// src/layout/textFlow.ts
|
|
490
496
|
var LINE_HEIGHT = 1.2;
|
|
491
497
|
var BULLET_INDENT = 1.1;
|
|
@@ -493,6 +499,8 @@ var BULLET_RADIUS = 0.2;
|
|
|
493
499
|
var RULE_GAP = 2.5;
|
|
494
500
|
var RULE_WIDTH = 0.9;
|
|
495
501
|
var WIDE = 1.6;
|
|
502
|
+
var TWO_COLUMN_MIN = 0.75;
|
|
503
|
+
var TWO_COLUMN_GAIN = 1.15;
|
|
496
504
|
var MIN_K = 0.02;
|
|
497
505
|
var STEPS = 22;
|
|
498
506
|
var ICON_SCALE = 1.6;
|
|
@@ -588,9 +596,6 @@ function keepTogether(groups, heights, cut) {
|
|
|
588
596
|
return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
|
|
589
597
|
}
|
|
590
598
|
function splitColumns(groups, zone) {
|
|
591
|
-
if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
|
|
592
|
-
return [{ col: zone, groups }];
|
|
593
|
-
}
|
|
594
599
|
const gap = Math.round(zone.w * 0.04);
|
|
595
600
|
const w = (zone.w - gap) / 2;
|
|
596
601
|
const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
|
|
@@ -607,16 +612,8 @@ function splitColumns(groups, zone) {
|
|
|
607
612
|
{ col: { ...zone, x: zone.x + w + gap, w }, groups: groups.slice(cut) }
|
|
608
613
|
];
|
|
609
614
|
}
|
|
610
|
-
function
|
|
611
|
-
const
|
|
612
|
-
group,
|
|
613
|
-
widths: group.lines.map((line) => {
|
|
614
|
-
ctx.font = fontOf(line, line.size, fonts);
|
|
615
|
-
return ctx.measureText(line.text).width;
|
|
616
|
-
})
|
|
617
|
-
}));
|
|
618
|
-
const columns = splitColumns(measured, zone);
|
|
619
|
-
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);
|
|
620
617
|
let lo = MIN_K;
|
|
621
618
|
let hi = 1;
|
|
622
619
|
if (fits(1)) {
|
|
@@ -630,6 +627,28 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
630
627
|
hi = mid;
|
|
631
628
|
}
|
|
632
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);
|
|
633
652
|
const out = emptyFlow();
|
|
634
653
|
const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
|
|
635
654
|
const end = Math.max(...heights);
|
|
@@ -645,26 +664,25 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
645
664
|
// src/render/regions.ts
|
|
646
665
|
var LINE_HEIGHT2 = 1.2;
|
|
647
666
|
var BODY_GAP = 40;
|
|
648
|
-
var BOTTOM_SIDE = 0.1;
|
|
649
667
|
function flowFlanks(ctx, groups, flanks, fonts) {
|
|
650
668
|
const half = Math.ceil(groups.length / 2);
|
|
651
669
|
return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
|
|
652
670
|
}
|
|
653
671
|
function smallestPerLine(groups, results) {
|
|
654
672
|
const sizes = results.flatMap((r3) => r3.texts.map((t) => t.box.h / LINE_HEIGHT2));
|
|
655
|
-
const
|
|
673
|
+
const smallest2 = [];
|
|
656
674
|
let i = 0;
|
|
657
675
|
groups.forEach(
|
|
658
676
|
(g) => g.lines.forEach((_line, j) => {
|
|
659
|
-
|
|
677
|
+
smallest2[j] = Math.min(smallest2[j] ?? Infinity, sizes[i] ?? Infinity);
|
|
660
678
|
i++;
|
|
661
679
|
})
|
|
662
680
|
);
|
|
663
|
-
return
|
|
681
|
+
return smallest2;
|
|
664
682
|
}
|
|
665
683
|
function equalFlanks(ctx, groups, flanks, fonts) {
|
|
666
|
-
const
|
|
667
|
-
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) })) }));
|
|
668
686
|
return flowFlanks(ctx, sized, flanks, fonts);
|
|
669
687
|
}
|
|
670
688
|
function measuredEnd(f, fallback) {
|
|
@@ -675,14 +693,76 @@ function pictureRegion(layout, geo, body, u, frame) {
|
|
|
675
693
|
if (layout !== "picture-bottom" /* PictureBottom */) {
|
|
676
694
|
return geo.picture;
|
|
677
695
|
}
|
|
678
|
-
const y = Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u);
|
|
679
|
-
|
|
680
|
-
return { x, y, w: frame.width - 2 * x, h: frame.height - y };
|
|
696
|
+
const y = Math.min(geo.picture.y, Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u));
|
|
697
|
+
return { x: geo.picture.x, y, w: geo.picture.w, h: frame.height - y };
|
|
681
698
|
}
|
|
682
699
|
|
|
683
|
-
// 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;
|
|
684
731
|
var BASE = 1080;
|
|
732
|
+
var LINE_HEIGHT3 = 1.2;
|
|
685
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;
|
|
686
766
|
var CARD_RADIUS = 0.035;
|
|
687
767
|
var CAP_TOP = 0.15;
|
|
688
768
|
var PIN_HEAD_Y = 0.08;
|
|
@@ -777,10 +857,6 @@ function capAligned(zone, head) {
|
|
|
777
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;
|
|
778
858
|
return { ...zone, y: zone.y + shift, h: zone.h - shift };
|
|
779
859
|
}
|
|
780
|
-
function splitHeader(header, hasLogo) {
|
|
781
|
-
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
782
|
-
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
783
|
-
}
|
|
784
860
|
function render(input, assets, canvas) {
|
|
785
861
|
const doc = checked(input, assets);
|
|
786
862
|
const { width, height } = FORMAT_SIZES[doc.format];
|
|
@@ -792,8 +868,8 @@ function render(input, assets, canvas) {
|
|
|
792
868
|
}
|
|
793
869
|
const palette = PALETTES[doc.palette];
|
|
794
870
|
const fonts = FONT_SETS[doc.fonts];
|
|
795
|
-
const
|
|
796
|
-
const
|
|
871
|
+
const u = Math.min(width, height) / BASE2;
|
|
872
|
+
const { geo, zones, head, body, sides } = fitToFloor(ctx, { doc, palette, fonts, width, height });
|
|
797
873
|
ctx.globalAlpha = 1;
|
|
798
874
|
ctx.fillStyle = palette.background;
|
|
799
875
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -801,11 +877,6 @@ function render(input, assets, canvas) {
|
|
|
801
877
|
ctx.fillStyle = palette.tint;
|
|
802
878
|
[geo.header, geo.body].forEach((r3) => ctx.fillRect(r3.x, r3.y, r3.w, r3.h));
|
|
803
879
|
}
|
|
804
|
-
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
805
|
-
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
806
|
-
const flanks = geo.flanks ?? [];
|
|
807
|
-
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
808
|
-
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
809
880
|
const radius = Math.min(width, height) * CARD_RADIUS;
|
|
810
881
|
[head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
|
|
811
882
|
const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
|
|
@@ -818,7 +889,7 @@ function render(input, assets, canvas) {
|
|
|
818
889
|
if (backdrop) {
|
|
819
890
|
drawArch(ctx, backdrop, palette.arch);
|
|
820
891
|
}
|
|
821
|
-
const region = pictureRegion(doc.layout, geo, body, u, {
|
|
892
|
+
const region = pictureRegion(doc.layout, geo, body, u, { height });
|
|
822
893
|
const picture = image && doc.picture ? placeContain(image, region, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
|
|
823
894
|
if (image && picture) {
|
|
824
895
|
ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
|