@dloizides/poster-render 0.2.0 → 0.2.2
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 +16 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +147 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -312,18 +312,19 @@ function headerGroups(doc, p, u) {
|
|
|
312
312
|
return [{ lines: lines.filter((x) => x !== void 0), gapAfter: 0 }];
|
|
313
313
|
}
|
|
314
314
|
function itemGroup(item, l, u) {
|
|
315
|
-
const lines = [l.script?.(item.date,
|
|
315
|
+
const lines = [l.script?.(item.date, 67)];
|
|
316
316
|
item.schedule.forEach((row) => lines.push(l.strong?.(`${row.label} ${row.time}`, 42)));
|
|
317
317
|
if (item.price) {
|
|
318
318
|
lines.push(l.accent?.(item.price, 42));
|
|
319
319
|
}
|
|
320
|
-
return { lines, gapAfter:
|
|
320
|
+
return { lines, gapAfter: 48 * u };
|
|
321
321
|
}
|
|
322
322
|
function eventGroups(doc, l, u, withPrices) {
|
|
323
323
|
const b = doc.blocks;
|
|
324
324
|
const groups = [];
|
|
325
325
|
b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
|
|
326
|
-
(withPrices ? b.prices : void 0)
|
|
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 }));
|
|
327
328
|
return groups;
|
|
328
329
|
}
|
|
329
330
|
function listGroups(doc, l, u) {
|
|
@@ -344,15 +345,15 @@ function bodyGroups(doc, p, u, withPrices = true) {
|
|
|
344
345
|
const groups = [];
|
|
345
346
|
const isList = doc.layout === "list" /* List */;
|
|
346
347
|
if (b.venue) {
|
|
347
|
-
groups.push({ lines: b.venue.map((v) => l.venue?.(v, isList ? 48 : 58)), gapAfter: 40 * u, rule: { color: p.accent, height: 8 * u } });
|
|
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 } });
|
|
348
349
|
}
|
|
349
350
|
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
|
|
350
351
|
if (b.includes) {
|
|
351
352
|
const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
|
|
352
|
-
groups.push({ lines, gapAfter: 24 * u,
|
|
353
|
+
groups.push({ lines, gapAfter: 24 * u, uniform: true });
|
|
353
354
|
}
|
|
354
355
|
if (b.footer) {
|
|
355
|
-
groups.push({ lines: [l.soft?.(b.footer,
|
|
356
|
+
groups.push({ lines: [l.soft?.(b.footer, 38)], gapAfter: 0 });
|
|
356
357
|
}
|
|
357
358
|
return groups;
|
|
358
359
|
}
|
|
@@ -388,11 +389,11 @@ var LAYOUT_SPECS = {
|
|
|
388
389
|
},
|
|
389
390
|
["desktop-safe" /* DesktopSafe */]: {
|
|
390
391
|
portrait: { header: [0.08, 0.06, 0.84, 0.24], body: [0.08, 0.31, 0.46, 0.58], picture: [0.56, 0.36, 0.44, 0.64], alignX: 1 },
|
|
391
|
-
landscape: { header: [0.0625, 0.05, 0.58, 0.44], body: [0.0625, 0.5, 0.58, 0.38], picture: [0.67, 0.0972, 0.29, 0.9028], alignX: 1 }
|
|
392
|
+
landscape: { header: [0.0625, 0.05, 0.58, 0.44], body: [0.0625, 0.5, 0.58, 0.38], picture: [0.67, 0.0972, 0.29, 0.9028], alignX: 1, logo: [0.4, 0.045, 0.09, 0.17] }
|
|
392
393
|
},
|
|
393
394
|
["desktop-arch" /* DesktopArch */]: {
|
|
394
395
|
portrait: { header: [0.08, 0.06, 0.84, 0.2], body: [0.08, 0.27, 0.84, 0.2], picture: [0.14, 0.48, 0.72, 0.52], alignX: 0.5, arch: 0.05 },
|
|
395
|
-
landscape: { header: [0.07, 0.3, 0.26, 0.45], body: [0.7, 0.25, 0.27, 0.65], picture: DESK_ARCH_PIC, alignX: 0.5, logo: [0.
|
|
396
|
+
landscape: { header: [0.07, 0.3, 0.26, 0.45], body: [0.7, 0.25, 0.27, 0.65], picture: DESK_ARCH_PIC, alignX: 0.5, logo: [0.86, 0.03, 0.11, 0.18], arch: 0.05 }
|
|
396
397
|
}
|
|
397
398
|
};
|
|
398
399
|
var r = Math.round;
|
|
@@ -494,8 +495,14 @@ var RULE_WIDTH = 0.9;
|
|
|
494
495
|
var WIDE = 1.6;
|
|
495
496
|
var MIN_K = 0.02;
|
|
496
497
|
var STEPS = 22;
|
|
498
|
+
var ICON_SCALE = 1.6;
|
|
499
|
+
var ICON_GAP = 0.35;
|
|
497
500
|
var fontOf = (line, size, fonts) => `${line.weight} ${size}px ${fonts[line.role]}`;
|
|
498
|
-
var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [] });
|
|
501
|
+
var emptyFlow = () => ({ texts: [], cards: [], rules: [], bullets: [], icons: [] });
|
|
502
|
+
var iconSize = (group, k) => {
|
|
503
|
+
const first = group.lines[0];
|
|
504
|
+
return group.icon && first ? first.size * k * LINE_HEIGHT * ICON_SCALE : 0;
|
|
505
|
+
};
|
|
499
506
|
function lineSize(line, width, k, avail) {
|
|
500
507
|
const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
|
|
501
508
|
return Math.max(0, Math.min(line.size * k, line.size * (avail - indent) / Math.max(width, 1)));
|
|
@@ -506,6 +513,14 @@ function groupRatio(m, k, avail) {
|
|
|
506
513
|
}
|
|
507
514
|
return Math.min(...m.group.lines.map((line, i) => lineSize(line, m.widths[i] ?? 1, k, avail) / line.size));
|
|
508
515
|
}
|
|
516
|
+
function placeIcon(group, d, x, textTop, textBottom, out) {
|
|
517
|
+
if (!group.icon || d <= 0) {
|
|
518
|
+
return textBottom;
|
|
519
|
+
}
|
|
520
|
+
const textH = textBottom - textTop;
|
|
521
|
+
out?.icons.push({ rect: { x, y: textTop + Math.max(0, (textH - d) / 2), w: d, h: d }, color: group.icon.color, glyph: group.icon.glyph });
|
|
522
|
+
return textTop + Math.max(textH, d);
|
|
523
|
+
}
|
|
509
524
|
function placeGroup(m, k, col, y0, out) {
|
|
510
525
|
const { group } = m;
|
|
511
526
|
let y = y0;
|
|
@@ -516,10 +531,13 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
516
531
|
const pad = group.card ? group.card.pad * k : 0;
|
|
517
532
|
const top = y;
|
|
518
533
|
y += pad;
|
|
519
|
-
const
|
|
534
|
+
const d = iconSize(group, k);
|
|
535
|
+
const iconIndent = d > 0 ? d * (1 + ICON_GAP) : 0;
|
|
536
|
+
const avail = col.w - 2 * pad - iconIndent;
|
|
537
|
+
const ratio = groupRatio(m, k, avail);
|
|
520
538
|
group.lines.forEach((line, i) => {
|
|
521
|
-
const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k,
|
|
522
|
-
const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
|
|
539
|
+
const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, avail) : line.size * ratio;
|
|
540
|
+
const indent = (line.bullet ? line.size * k * BULLET_INDENT : 0) + iconIndent;
|
|
523
541
|
const w = (m.widths[i] ?? 0) * size / line.size;
|
|
524
542
|
const x = col.right ? col.x + col.w - pad - w : col.x + pad + indent;
|
|
525
543
|
out?.texts.push({ text: line.text, font: "", color: line.color, box: { x, y, w, h: size * LINE_HEIGHT } });
|
|
@@ -528,36 +546,62 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
528
546
|
}
|
|
529
547
|
y += size * LINE_HEIGHT;
|
|
530
548
|
});
|
|
549
|
+
y = placeIcon(group, d, col.x + pad, top + pad, y, out);
|
|
531
550
|
y += pad;
|
|
551
|
+
if (group.rule && group.ruleBelow) {
|
|
552
|
+
y += group.rule.height * k * (RULE_GAP - 1);
|
|
553
|
+
out?.rules.push({ rect: { x: col.x, y, w: col.w * RULE_WIDTH, h: group.rule.height * k }, color: group.rule.color });
|
|
554
|
+
y += group.rule.height * k;
|
|
555
|
+
}
|
|
532
556
|
if (group.card) {
|
|
533
557
|
out?.cards.unshift({ rect: { x: col.x, y: top, w: col.w, h: y - top }, color: group.card.color });
|
|
534
558
|
}
|
|
535
559
|
return y + group.gapAfter * k - y0;
|
|
536
560
|
}
|
|
537
|
-
function placeColumn(groups, k, col, out) {
|
|
561
|
+
function placeColumn(groups, k, col, out, spread = 0) {
|
|
538
562
|
let h = 0;
|
|
539
563
|
for (const g of groups) {
|
|
540
|
-
h += placeGroup(g, k, col, col.y + h, out);
|
|
564
|
+
h += placeGroup(g, k, col, col.y + h, out) + spread;
|
|
565
|
+
}
|
|
566
|
+
return h - spread;
|
|
567
|
+
}
|
|
568
|
+
var columnSum = (heights, from, to) => heights.slice(from, to).reduce((a, b) => a + b, 0);
|
|
569
|
+
var tallest = (heights, cut) => {
|
|
570
|
+
const left = heights.slice(0, cut).reduce((a, b) => a + b, 0);
|
|
571
|
+
const right = heights.slice(cut).reduce((a, b) => a + b, 0);
|
|
572
|
+
return Math.max(left, right);
|
|
573
|
+
};
|
|
574
|
+
function keepTogether(groups, heights, cut) {
|
|
575
|
+
const breaks = (c) => groups[c - 1]?.group.keepWithNext === true;
|
|
576
|
+
let back = cut;
|
|
577
|
+
while (back > 1 && breaks(back)) {
|
|
578
|
+
back--;
|
|
579
|
+
}
|
|
580
|
+
let fwd = cut;
|
|
581
|
+
while (fwd < groups.length - 1 && breaks(fwd)) {
|
|
582
|
+
fwd++;
|
|
583
|
+
}
|
|
584
|
+
const candidates = [back, fwd].filter((c) => !breaks(c));
|
|
585
|
+
if (candidates.length === 0) {
|
|
586
|
+
return cut;
|
|
541
587
|
}
|
|
542
|
-
return
|
|
588
|
+
return candidates.reduce((best, c) => tallest(heights, c) < tallest(heights, best) ? c : best);
|
|
543
589
|
}
|
|
544
590
|
function splitColumns(groups, zone) {
|
|
545
591
|
if (zone.w / Math.max(zone.h, 1) <= WIDE || groups.length < 2) {
|
|
546
592
|
return [{ col: zone, groups }];
|
|
547
593
|
}
|
|
548
|
-
const heights = groups.map((g) => placeGroup(g, 1, zone, 0, null));
|
|
549
|
-
const half = heights.reduce((a, b) => a + b, 0) / 2;
|
|
550
|
-
let acc = 0;
|
|
551
|
-
let cut = 1;
|
|
552
|
-
heights.forEach((h, i) => {
|
|
553
|
-
if (acc < half) {
|
|
554
|
-
cut = i + 1;
|
|
555
|
-
}
|
|
556
|
-
acc += h;
|
|
557
|
-
});
|
|
558
|
-
cut = Math.min(cut, groups.length - 1);
|
|
559
594
|
const gap = Math.round(zone.w * 0.04);
|
|
560
595
|
const w = (zone.w - gap) / 2;
|
|
596
|
+
const heights = groups.map((g) => placeGroup(g, 1, { ...zone, w }, 0, null));
|
|
597
|
+
const total = columnSum(heights, 0, heights.length);
|
|
598
|
+
let cut = 1;
|
|
599
|
+
for (let c = 2; c < groups.length; c++) {
|
|
600
|
+
if (Math.abs(total - 2 * columnSum(heights, 0, c)) < Math.abs(total - 2 * columnSum(heights, 0, cut))) {
|
|
601
|
+
cut = c;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
cut = keepTogether(groups, heights, cut);
|
|
561
605
|
return [
|
|
562
606
|
{ col: { ...zone, w }, groups: groups.slice(0, cut) },
|
|
563
607
|
{ col: { ...zone, x: zone.x + w + gap, w }, groups: groups.slice(cut) }
|
|
@@ -587,7 +631,9 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
587
631
|
}
|
|
588
632
|
}
|
|
589
633
|
const out = emptyFlow();
|
|
590
|
-
columns.
|
|
634
|
+
const heights = columns.map((c) => placeColumn(c.groups, lo, c.col, null));
|
|
635
|
+
const end = Math.max(...heights);
|
|
636
|
+
columns.forEach((c, i) => placeColumn(c.groups, lo, c.col, out, c.groups.length > 1 && columns.length > 1 ? (end - (heights[i] ?? end)) / (c.groups.length - 1) : 0));
|
|
591
637
|
const lines = groups.flatMap((g) => g.lines);
|
|
592
638
|
out.texts.forEach((t, i) => {
|
|
593
639
|
const line = lines[i];
|
|
@@ -596,11 +642,75 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
596
642
|
return out;
|
|
597
643
|
}
|
|
598
644
|
|
|
645
|
+
// src/render/regions.ts
|
|
646
|
+
var LINE_HEIGHT2 = 1.2;
|
|
647
|
+
var BODY_GAP = 40;
|
|
648
|
+
var BOTTOM_SIDE = 0.1;
|
|
649
|
+
function flowFlanks(ctx, groups, flanks, fonts) {
|
|
650
|
+
const half = Math.ceil(groups.length / 2);
|
|
651
|
+
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
|
+
}
|
|
653
|
+
function smallestPerLine(groups, results) {
|
|
654
|
+
const sizes = results.flatMap((r3) => r3.texts.map((t) => t.box.h / LINE_HEIGHT2));
|
|
655
|
+
const smallest = [];
|
|
656
|
+
let i = 0;
|
|
657
|
+
groups.forEach(
|
|
658
|
+
(g) => g.lines.forEach((_line, j) => {
|
|
659
|
+
smallest[j] = Math.min(smallest[j] ?? Infinity, sizes[i] ?? Infinity);
|
|
660
|
+
i++;
|
|
661
|
+
})
|
|
662
|
+
);
|
|
663
|
+
return smallest;
|
|
664
|
+
}
|
|
665
|
+
function equalFlanks(ctx, groups, flanks, fonts) {
|
|
666
|
+
const smallest = smallestPerLine(groups, flowFlanks(ctx, groups, flanks, fonts));
|
|
667
|
+
const sized = groups.map((g) => ({ ...g, lines: g.lines.map((line, j) => ({ ...line, size: Math.min(line.size, smallest[j] ?? line.size) })) }));
|
|
668
|
+
return flowFlanks(ctx, sized, flanks, fonts);
|
|
669
|
+
}
|
|
670
|
+
function measuredEnd(f, fallback) {
|
|
671
|
+
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);
|
|
672
|
+
return bottoms.length > 0 ? Math.max(...bottoms) : fallback;
|
|
673
|
+
}
|
|
674
|
+
function pictureRegion(layout, geo, body, u, frame) {
|
|
675
|
+
if (layout !== "picture-bottom" /* PictureBottom */) {
|
|
676
|
+
return geo.picture;
|
|
677
|
+
}
|
|
678
|
+
const y = Math.ceil(measuredEnd(body, geo.body.y) + BODY_GAP * u);
|
|
679
|
+
const x = Math.round(frame.width * BOTTOM_SIDE);
|
|
680
|
+
return { x, y, w: frame.width - 2 * x, h: frame.height - y };
|
|
681
|
+
}
|
|
682
|
+
|
|
599
683
|
// src/render/render.ts
|
|
600
684
|
var BASE = 1080;
|
|
601
685
|
var LOGO_SHARE = 0.26;
|
|
602
686
|
var CARD_RADIUS = 0.035;
|
|
603
687
|
var CAP_TOP = 0.15;
|
|
688
|
+
var PIN_HEAD_Y = 0.08;
|
|
689
|
+
var PIN_HEAD_R = 0.19;
|
|
690
|
+
var PIN_TIP_Y = 0.28;
|
|
691
|
+
var PIN_HOLE = 0.42;
|
|
692
|
+
var ARC_START = 0.75;
|
|
693
|
+
var ARC_END = 2.25;
|
|
694
|
+
function drawPin(ctx, r3, color, glyph) {
|
|
695
|
+
const cx = r3.x + r3.w / 2;
|
|
696
|
+
const cy = r3.y + r3.h / 2;
|
|
697
|
+
const hy = cy - r3.w * PIN_HEAD_Y;
|
|
698
|
+
const rad = r3.w * PIN_HEAD_R;
|
|
699
|
+
ctx.fillStyle = color;
|
|
700
|
+
ctx.beginPath();
|
|
701
|
+
ctx.arc(cx, cy, r3.w / 2, 0, Math.PI * 2);
|
|
702
|
+
ctx.fill();
|
|
703
|
+
ctx.fillStyle = glyph;
|
|
704
|
+
ctx.beginPath();
|
|
705
|
+
ctx.moveTo(cx, cy + r3.w * PIN_TIP_Y);
|
|
706
|
+
ctx.arc(cx, hy, rad, Math.PI * ARC_START, Math.PI * ARC_END);
|
|
707
|
+
ctx.closePath();
|
|
708
|
+
ctx.fill();
|
|
709
|
+
ctx.fillStyle = color;
|
|
710
|
+
ctx.beginPath();
|
|
711
|
+
ctx.arc(cx, hy, rad * PIN_HOLE, 0, Math.PI * 2);
|
|
712
|
+
ctx.fill();
|
|
713
|
+
}
|
|
604
714
|
function roundRect(ctx, r3, radius) {
|
|
605
715
|
const rad = Math.min(radius, r3.w / 2, r3.h / 2);
|
|
606
716
|
ctx.beginPath();
|
|
@@ -627,6 +737,7 @@ function drawFlow(ctx, f, radius) {
|
|
|
627
737
|
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
|
|
628
738
|
ctx.fill();
|
|
629
739
|
});
|
|
740
|
+
f.icons.forEach((i) => drawPin(ctx, i.rect, i.color, i.glyph));
|
|
630
741
|
ctx.textAlign = "left";
|
|
631
742
|
ctx.textBaseline = "top";
|
|
632
743
|
f.texts.forEach((t) => {
|
|
@@ -661,10 +772,6 @@ function drawArch(ctx, r3, color) {
|
|
|
661
772
|
ctx.arc(r3.x + rad, r3.y + rad, rad, Math.PI, Math.PI * 2);
|
|
662
773
|
ctx.fill();
|
|
663
774
|
}
|
|
664
|
-
function flankFlows(ctx, groups, flanks, fonts) {
|
|
665
|
-
const half = Math.ceil(groups.length / 2);
|
|
666
|
-
return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
|
|
667
|
-
}
|
|
668
775
|
function capAligned(zone, head) {
|
|
669
776
|
const first = head.texts[0]?.box;
|
|
670
777
|
const shift = first ? Math.min(Math.max(0, Math.round(first.y + first.h * CAP_TOP) - zone.y), Math.round(zone.h / 2)) : 0;
|
|
@@ -698,20 +805,21 @@ function render(input, assets, canvas) {
|
|
|
698
805
|
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
699
806
|
const flanks = geo.flanks ?? [];
|
|
700
807
|
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
701
|
-
const sides = flanks.length > 0 ?
|
|
808
|
+
const sides = flanks.length > 0 ? equalFlanks(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
702
809
|
const radius = Math.min(width, height) * CARD_RADIUS;
|
|
703
810
|
[head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
|
|
704
811
|
const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
ctx.drawImage(logoImage,
|
|
812
|
+
const logo = logoImage ? placeContain(logoImage, geo.logo ? zones.logo : capAligned(zones.logo, head), "top" /* Top */, 1, 1) : null;
|
|
813
|
+
if (logoImage && logo) {
|
|
814
|
+
ctx.drawImage(logoImage, logo.x, logo.y, logo.w, logo.h);
|
|
708
815
|
}
|
|
709
816
|
const image = doc.picture ? assets[doc.picture.imageId] : void 0;
|
|
710
817
|
const backdrop = image ? archRect(geo) : null;
|
|
711
818
|
if (backdrop) {
|
|
712
819
|
drawArch(ctx, backdrop, palette.arch);
|
|
713
820
|
}
|
|
714
|
-
const
|
|
821
|
+
const region = pictureRegion(doc.layout, geo, body, u, { width, height });
|
|
822
|
+
const picture = image && doc.picture ? placeContain(image, region, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
|
|
715
823
|
if (image && picture) {
|
|
716
824
|
ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
|
|
717
825
|
}
|
|
@@ -719,9 +827,11 @@ function render(input, assets, canvas) {
|
|
|
719
827
|
width,
|
|
720
828
|
height,
|
|
721
829
|
picture,
|
|
722
|
-
pictureRegion:
|
|
830
|
+
pictureRegion: region,
|
|
723
831
|
textBoxes: [head, body, ...sides].flatMap((f) => f.texts.map((t) => t.box)),
|
|
724
832
|
cardBoxes: body.cards.map((c) => c.rect),
|
|
833
|
+
iconBoxes: [head, body, ...sides].flatMap((f) => f.icons.map((i) => i.rect)),
|
|
834
|
+
logo,
|
|
725
835
|
backdrop
|
|
726
836
|
};
|
|
727
837
|
}
|