@dloizides/poster-render 0.1.0 → 0.2.0
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/README.md +2 -1
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +174 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Layouts `arch`, `phone-clock-safe`, `phone-arch`, `desktop-safe`, `desktop-arch` (FLIER-1 D4 "Keep all layouts"); `LAYOUT_CATALOGUE` + `defaultLayoutFor`.
|
|
6
|
+
- Critic r2 nits: arch prices moved up beside the arch top, lighter arch fill, phone arch top at 63% height, desktop arch 40 px right, logo top on the title cap top.
|
|
7
|
+
- Includes block uses one size for every line; `RenderReport.backdrop`; `Palette.arch`.
|
|
8
|
+
|
|
3
9
|
## 0.1.0
|
|
4
10
|
|
|
5
11
|
- FlierDoc schema + validator, render, exportPng, 4 layouts, 5 formats, palettes, font sets, Kizomba example.
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ One JSON document (`FlierDoc`) in, one poster out. Used by Flier Studio (FLIER-1
|
|
|
7
7
|
- `render(doc, assets, canvas)`: validates first (throws `FlierDocError` before touching the canvas), resizes the canvas to the format, draws, returns a `RenderReport` (picture rect, text and card boxes).
|
|
8
8
|
- `exportPng(canvas)`: PNG `Blob` of the canvas size.
|
|
9
9
|
- Formats: story 1080x1920, post 1080x1350, square 1080x1080, phone 1170x2532, desktop 2560x1440.
|
|
10
|
-
- Layouts: `picture-right
|
|
10
|
+
- Layouts: `picture-right` (E-A), `picture-left` (E-B), `arch` (E-C), `picture-bottom`, `list`, `phone-clock-safe` (P-A, top kept clear for the lock-screen clock), `phone-arch` (P-B), `desktop-safe` (D-A, text clear of desktop icons and the taskbar), `desktop-arch` (D-B). `LAYOUT_CATALOGUE` lists id, design id, label, description and designed formats (for the app picker and the LLM prompt); `defaultLayoutFor(format)` gives E-A / P-A / D-A.
|
|
11
|
+
- Arch layouts paint a soft arch (palette `arch`, about 4% off the ground) behind the picture only, inside the picture region; prices sit large on either side. The includes block is set at one size for every line. Palettes: `sand`, `night`, `sea`, `rose`. Font sets: `classic`, `modern`, `elegant` (the app loads the web fonts).
|
|
11
12
|
- The picture is placed contain + anchor in its own region: never cropped, never faded, drawn last, and no text or card is laid out in its region. Text shrinks to fit, never overflows.
|
|
12
13
|
- Example: `kizombaWorkshopDocs` (3 workshops + list flier), `kizombaWorkshop` (the list flier).
|
package/dist/index.d.mts
CHANGED
|
@@ -21,7 +21,12 @@ declare enum LayoutId {
|
|
|
21
21
|
PictureRight = "picture-right",
|
|
22
22
|
PictureLeft = "picture-left",
|
|
23
23
|
PictureBottom = "picture-bottom",
|
|
24
|
-
List = "list"
|
|
24
|
+
List = "list",
|
|
25
|
+
Arch = "arch",
|
|
26
|
+
PhoneClockSafe = "phone-clock-safe",
|
|
27
|
+
PhoneArch = "phone-arch",
|
|
28
|
+
DesktopSafe = "desktop-safe",
|
|
29
|
+
DesktopArch = "desktop-arch"
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
declare enum PaletteId {
|
|
@@ -135,6 +140,7 @@ interface RenderReport {
|
|
|
135
140
|
pictureRegion: Rect;
|
|
136
141
|
textBoxes: Rect[];
|
|
137
142
|
cardBoxes: Rect[];
|
|
143
|
+
backdrop: Rect | null;
|
|
138
144
|
}
|
|
139
145
|
interface Palette {
|
|
140
146
|
background: string;
|
|
@@ -144,6 +150,7 @@ interface Palette {
|
|
|
144
150
|
ink: string;
|
|
145
151
|
soft: string;
|
|
146
152
|
card: string;
|
|
153
|
+
arch: string;
|
|
147
154
|
}
|
|
148
155
|
interface FontSet {
|
|
149
156
|
display: string;
|
|
@@ -200,7 +207,18 @@ interface DataUrlCanvas {
|
|
|
200
207
|
/** Encodes the canvas as a PNG Blob of the canvas's exact pixel size. */
|
|
201
208
|
declare function exportPng(canvas: DataUrlCanvas): Blob;
|
|
202
209
|
|
|
210
|
+
interface LayoutCatalogueEntry {
|
|
211
|
+
id: LayoutId;
|
|
212
|
+
designId: string;
|
|
213
|
+
label: string;
|
|
214
|
+
description: string;
|
|
215
|
+
formats: readonly FormatId[];
|
|
216
|
+
}
|
|
217
|
+
declare const LAYOUT_CATALOGUE: readonly LayoutCatalogueEntry[];
|
|
218
|
+
/** The default layout for a format (D4: E-A for posters, P-A for phone, D-A for desktop). */
|
|
219
|
+
declare function defaultLayoutFor(format: FormatId): LayoutId;
|
|
220
|
+
|
|
203
221
|
declare const kizombaWorkshop: FlierDoc;
|
|
204
222
|
declare const kizombaWorkshopDocs: FlierDoc[];
|
|
205
223
|
|
|
206
|
-
export { BackgroundStyle, type DataUrlCanvas, FONT_SETS, FORMAT_SIZES, type FlierBlocks, type FlierDoc, FlierDocError, type FlierDocResult, type FontSet, FontSetId, FormatId, type FormatSize, type IncludesBlock, type JsonSchema, LayoutId, type ListItem, type LogoSlot, PALETTES, type Palette, PaletteId, PictureAnchor, type PictureSlot, type PosterAssets, type PosterCanvas, type PosterContext, type PosterImage, type PriceRow, type Rect, type RenderReport, type ScheduleRow, type TextMetricsLike, exportPng, flierDocSchema, kizombaWorkshop, kizombaWorkshopDocs, render, validateFlierDoc };
|
|
224
|
+
export { BackgroundStyle, type DataUrlCanvas, FONT_SETS, FORMAT_SIZES, type FlierBlocks, type FlierDoc, FlierDocError, type FlierDocResult, type FontSet, FontSetId, FormatId, type FormatSize, type IncludesBlock, type JsonSchema, LAYOUT_CATALOGUE, type LayoutCatalogueEntry, LayoutId, type ListItem, type LogoSlot, PALETTES, type Palette, PaletteId, PictureAnchor, type PictureSlot, type PosterAssets, type PosterCanvas, type PosterContext, type PosterImage, type PriceRow, type Rect, type RenderReport, type ScheduleRow, type TextMetricsLike, defaultLayoutFor, exportPng, flierDocSchema, kizombaWorkshop, kizombaWorkshopDocs, render, validateFlierDoc };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,12 @@ declare enum LayoutId {
|
|
|
21
21
|
PictureRight = "picture-right",
|
|
22
22
|
PictureLeft = "picture-left",
|
|
23
23
|
PictureBottom = "picture-bottom",
|
|
24
|
-
List = "list"
|
|
24
|
+
List = "list",
|
|
25
|
+
Arch = "arch",
|
|
26
|
+
PhoneClockSafe = "phone-clock-safe",
|
|
27
|
+
PhoneArch = "phone-arch",
|
|
28
|
+
DesktopSafe = "desktop-safe",
|
|
29
|
+
DesktopArch = "desktop-arch"
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
declare enum PaletteId {
|
|
@@ -135,6 +140,7 @@ interface RenderReport {
|
|
|
135
140
|
pictureRegion: Rect;
|
|
136
141
|
textBoxes: Rect[];
|
|
137
142
|
cardBoxes: Rect[];
|
|
143
|
+
backdrop: Rect | null;
|
|
138
144
|
}
|
|
139
145
|
interface Palette {
|
|
140
146
|
background: string;
|
|
@@ -144,6 +150,7 @@ interface Palette {
|
|
|
144
150
|
ink: string;
|
|
145
151
|
soft: string;
|
|
146
152
|
card: string;
|
|
153
|
+
arch: string;
|
|
147
154
|
}
|
|
148
155
|
interface FontSet {
|
|
149
156
|
display: string;
|
|
@@ -200,7 +207,18 @@ interface DataUrlCanvas {
|
|
|
200
207
|
/** Encodes the canvas as a PNG Blob of the canvas's exact pixel size. */
|
|
201
208
|
declare function exportPng(canvas: DataUrlCanvas): Blob;
|
|
202
209
|
|
|
210
|
+
interface LayoutCatalogueEntry {
|
|
211
|
+
id: LayoutId;
|
|
212
|
+
designId: string;
|
|
213
|
+
label: string;
|
|
214
|
+
description: string;
|
|
215
|
+
formats: readonly FormatId[];
|
|
216
|
+
}
|
|
217
|
+
declare const LAYOUT_CATALOGUE: readonly LayoutCatalogueEntry[];
|
|
218
|
+
/** The default layout for a format (D4: E-A for posters, P-A for phone, D-A for desktop). */
|
|
219
|
+
declare function defaultLayoutFor(format: FormatId): LayoutId;
|
|
220
|
+
|
|
203
221
|
declare const kizombaWorkshop: FlierDoc;
|
|
204
222
|
declare const kizombaWorkshopDocs: FlierDoc[];
|
|
205
223
|
|
|
206
|
-
export { BackgroundStyle, type DataUrlCanvas, FONT_SETS, FORMAT_SIZES, type FlierBlocks, type FlierDoc, FlierDocError, type FlierDocResult, type FontSet, FontSetId, FormatId, type FormatSize, type IncludesBlock, type JsonSchema, LayoutId, type ListItem, type LogoSlot, PALETTES, type Palette, PaletteId, PictureAnchor, type PictureSlot, type PosterAssets, type PosterCanvas, type PosterContext, type PosterImage, type PriceRow, type Rect, type RenderReport, type ScheduleRow, type TextMetricsLike, exportPng, flierDocSchema, kizombaWorkshop, kizombaWorkshopDocs, render, validateFlierDoc };
|
|
224
|
+
export { BackgroundStyle, type DataUrlCanvas, FONT_SETS, FORMAT_SIZES, type FlierBlocks, type FlierDoc, FlierDocError, type FlierDocResult, type FontSet, FontSetId, FormatId, type FormatSize, type IncludesBlock, type JsonSchema, LAYOUT_CATALOGUE, type LayoutCatalogueEntry, LayoutId, type ListItem, type LogoSlot, PALETTES, type Palette, PaletteId, PictureAnchor, type PictureSlot, type PosterAssets, type PosterCanvas, type PosterContext, type PosterImage, type PriceRow, type Rect, type RenderReport, type ScheduleRow, type TextMetricsLike, defaultLayoutFor, exportPng, flierDocSchema, kizombaWorkshop, kizombaWorkshopDocs, render, validateFlierDoc };
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,11 @@ var LayoutId = /* @__PURE__ */ ((LayoutId2) => {
|
|
|
31
31
|
LayoutId2["PictureLeft"] = "picture-left";
|
|
32
32
|
LayoutId2["PictureBottom"] = "picture-bottom";
|
|
33
33
|
LayoutId2["List"] = "list";
|
|
34
|
+
LayoutId2["Arch"] = "arch";
|
|
35
|
+
LayoutId2["PhoneClockSafe"] = "phone-clock-safe";
|
|
36
|
+
LayoutId2["PhoneArch"] = "phone-arch";
|
|
37
|
+
LayoutId2["DesktopSafe"] = "desktop-safe";
|
|
38
|
+
LayoutId2["DesktopArch"] = "desktop-arch";
|
|
34
39
|
return LayoutId2;
|
|
35
40
|
})(LayoutId || {});
|
|
36
41
|
|
|
@@ -62,10 +67,10 @@ var FORMAT_SIZES = {
|
|
|
62
67
|
|
|
63
68
|
// src/palettes.ts
|
|
64
69
|
var PALETTES = {
|
|
65
|
-
["sand" /* Sand */]: { background: "#f5f4f2", tint: "#ece8e3", title: "#958a80", accent: "#e9b47f", ink: "#5f564f", soft: "#b3a89e", card: "#ffffff" },
|
|
66
|
-
["night" /* Night */]: { background: "#1c1a17", tint: "#25221e", title: "#e8dccb", accent: "#e9b47f", ink: "#f4efe8", soft: "#9c9284", card: "#2c2824" },
|
|
67
|
-
["sea" /* Sea */]: { background: "#eef4f3", tint: "#e1ecea", title: "#4f7d7a", accent: "#e07a5f", ink: "#2f3e46", soft: "#8aa3a1", card: "#ffffff" },
|
|
68
|
-
["rose" /* Rose */]: { background: "#f8efec", tint: "#f1e2dd", title: "#9b6b63", accent: "#c9846f", ink: "#4a3934", soft: "#b39a92", card: "#ffffff" }
|
|
70
|
+
["sand" /* Sand */]: { background: "#f5f4f2", tint: "#ece8e3", title: "#958a80", accent: "#e9b47f", ink: "#5f564f", soft: "#b3a89e", card: "#ffffff", arch: "#ede8e2" },
|
|
71
|
+
["night" /* Night */]: { background: "#1c1a17", tint: "#25221e", title: "#e8dccb", accent: "#e9b47f", ink: "#f4efe8", soft: "#9c9284", card: "#2c2824", arch: "#25221f" },
|
|
72
|
+
["sea" /* Sea */]: { background: "#eef4f3", tint: "#e1ecea", title: "#4f7d7a", accent: "#e07a5f", ink: "#2f3e46", soft: "#8aa3a1", card: "#ffffff", arch: "#e4ecea" },
|
|
73
|
+
["rose" /* Rose */]: { background: "#f8efec", tint: "#f1e2dd", title: "#9b6b63", accent: "#c9846f", ink: "#4a3934", soft: "#b39a92", card: "#ffffff", arch: "#f1e5e1" }
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
// src/fontSets.ts
|
|
@@ -316,11 +321,11 @@ function itemGroup(item, l, u) {
|
|
|
316
321
|
}
|
|
317
322
|
return { lines, gapAfter: 26 * u };
|
|
318
323
|
}
|
|
319
|
-
function eventGroups(doc, l, u) {
|
|
324
|
+
function eventGroups(doc, l, u, withPrices) {
|
|
320
325
|
const b = doc.blocks;
|
|
321
326
|
const groups = [];
|
|
322
327
|
b.schedule?.forEach((row) => groups.push({ lines: [l.strong?.(row.label, 66), l.accent?.(row.time, 60)], gapAfter: 30 * u }));
|
|
323
|
-
b.prices?.forEach((row) => groups.push({ lines: [l.strong?.(row.amount, 66), l.accent?.(row.label, 52)], gapAfter: 30 * u }));
|
|
328
|
+
(withPrices ? b.prices : void 0)?.forEach((row) => groups.push({ lines: [l.strong?.(row.amount, 66), l.accent?.(row.label, 52)], gapAfter: 30 * u }));
|
|
324
329
|
return groups;
|
|
325
330
|
}
|
|
326
331
|
function listGroups(doc, l, u) {
|
|
@@ -331,7 +336,11 @@ function listGroups(doc, l, u) {
|
|
|
331
336
|
}
|
|
332
337
|
return groups;
|
|
333
338
|
}
|
|
334
|
-
function
|
|
339
|
+
function flankPriceGroups(doc, p, u) {
|
|
340
|
+
const l = makers(p, u);
|
|
341
|
+
return (doc.blocks.prices ?? []).map((row) => ({ lines: [l.strong?.(row.amount, 110), l.accent?.(row.label, 48)], gapAfter: 30 * u }));
|
|
342
|
+
}
|
|
343
|
+
function bodyGroups(doc, p, u, withPrices = true) {
|
|
335
344
|
const l = makers(p, u);
|
|
336
345
|
const b = doc.blocks;
|
|
337
346
|
const groups = [];
|
|
@@ -339,10 +348,10 @@ function bodyGroups(doc, p, u) {
|
|
|
339
348
|
if (b.venue) {
|
|
340
349
|
groups.push({ lines: b.venue.map((v) => l.venue?.(v, isList ? 48 : 58)), gapAfter: 40 * u, rule: { color: p.accent, height: 8 * u } });
|
|
341
350
|
}
|
|
342
|
-
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u));
|
|
351
|
+
groups.push(...isList ? listGroups(doc, l, u) : eventGroups(doc, l, u, withPrices));
|
|
343
352
|
if (b.includes) {
|
|
344
|
-
const lines = [l.accent?.(b.includes.heading,
|
|
345
|
-
groups.push({ lines, gapAfter: 24 * u, card: { color: p.card, pad: 28 * u } });
|
|
353
|
+
const lines = [l.accent?.(b.includes.heading, 38), ...b.includes.items.map((i) => l.item?.(i, 38))];
|
|
354
|
+
groups.push({ lines, gapAfter: 24 * u, card: { color: p.card, pad: 28 * u }, uniform: true });
|
|
346
355
|
}
|
|
347
356
|
if (b.footer) {
|
|
348
357
|
groups.push({ lines: [l.soft?.(b.footer, 34)], gapAfter: 0 });
|
|
@@ -350,6 +359,51 @@ function bodyGroups(doc, p, u) {
|
|
|
350
359
|
return groups;
|
|
351
360
|
}
|
|
352
361
|
|
|
362
|
+
// src/layout/layoutSpecs.ts
|
|
363
|
+
var flanksBeside = (pic, top, bottom2, m) => [
|
|
364
|
+
[m, top, pic[0] - 2 * m, bottom2 - top],
|
|
365
|
+
[pic[0] + pic[2] + m, top, 1 - pic[0] - pic[2] - 2 * m, bottom2 - top]
|
|
366
|
+
];
|
|
367
|
+
var ARCH_PIC = [0.22, 0.42, 0.56, 0.58];
|
|
368
|
+
var PHONE_ARCH_PIC = [0.2, 0.6, 0.6, 0.4];
|
|
369
|
+
var DESK_ARCH_PIC = [0.355, 0.07, 0.32, 0.93];
|
|
370
|
+
var ARCH_LANDSCAPE = {
|
|
371
|
+
header: [0.04, 0.06, 0.3, 0.5],
|
|
372
|
+
body: [0.04, 0.58, 0.3, 0.36],
|
|
373
|
+
picture: [0.36, 0.08, 0.3, 0.92],
|
|
374
|
+
alignX: 0.5,
|
|
375
|
+
flanks: [[0.68, 0.08, 0.28, 0.42], [0.68, 0.52, 0.28, 0.42]],
|
|
376
|
+
arch: 0.06
|
|
377
|
+
};
|
|
378
|
+
var LAYOUT_SPECS = {
|
|
379
|
+
["arch" /* Arch */]: {
|
|
380
|
+
portrait: { header: [0.045, 0.025, 0.91, 0.18], body: [0.045, 0.215, 0.91, 0.19], picture: ARCH_PIC, alignX: 0.5, flanks: flanksBeside(ARCH_PIC, 0.44, 0.97, 0.03), arch: 0.04 },
|
|
381
|
+
landscape: ARCH_LANDSCAPE
|
|
382
|
+
},
|
|
383
|
+
["phone-clock-safe" /* PhoneClockSafe */]: {
|
|
384
|
+
portrait: { header: [0.045, 0.28, 0.91, 0.2], body: [0.045, 0.5, 0.42, 0.44], picture: [0.48, 0.5, 0.52, 0.5], alignX: 1 },
|
|
385
|
+
landscape: { header: [0.04, 0.2, 0.52, 0.3], body: [0.04, 0.52, 0.52, 0.42], picture: [0.6, 0.12, 0.4, 0.88], alignX: 1 }
|
|
386
|
+
},
|
|
387
|
+
["phone-arch" /* PhoneArch */]: {
|
|
388
|
+
portrait: { header: [0.045, 0.3, 0.91, 0.13], body: [0.045, 0.44, 0.91, 0.14], picture: PHONE_ARCH_PIC, alignX: 0.5, flanks: flanksBeside(PHONE_ARCH_PIC, 0.63, 0.94, 0.03), arch: 0.08 },
|
|
389
|
+
landscape: { ...ARCH_LANDSCAPE, header: [0.04, 0.16, 0.3, 0.42], picture: [0.36, 0.16, 0.3, 0.84] }
|
|
390
|
+
},
|
|
391
|
+
["desktop-safe" /* DesktopSafe */]: {
|
|
392
|
+
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 },
|
|
393
|
+
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 }
|
|
394
|
+
},
|
|
395
|
+
["desktop-arch" /* DesktopArch */]: {
|
|
396
|
+
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 },
|
|
397
|
+
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.88, 0.04, 0.09, 0.14], arch: 0.05 }
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var r = Math.round;
|
|
401
|
+
var toRect = (f, w, h) => {
|
|
402
|
+
const x = r(f[0] * w);
|
|
403
|
+
const y = r(f[1] * h);
|
|
404
|
+
return { x, y, w: r((f[0] + f[2]) * w) - x, h: r((f[1] + f[3]) * h) - y };
|
|
405
|
+
};
|
|
406
|
+
|
|
353
407
|
// src/layout/geometry.ts
|
|
354
408
|
var MARGIN = 0.045;
|
|
355
409
|
var HEADER_PORTRAIT = 0.3;
|
|
@@ -362,12 +416,12 @@ var PICTURE_SIDE = {
|
|
|
362
416
|
["list" /* List */]: [0.5, 0.36]
|
|
363
417
|
};
|
|
364
418
|
var ANCHOR_Y = { ["top" /* Top */]: 0, ["center" /* Center */]: 0.5, ["bottom" /* Bottom */]: 1 };
|
|
365
|
-
var
|
|
419
|
+
var r2 = Math.round;
|
|
366
420
|
function side(w, h, fraction) {
|
|
367
|
-
const m =
|
|
421
|
+
const m = r2(Math.min(w, h) * MARGIN);
|
|
368
422
|
if (h >= w) {
|
|
369
|
-
const split2 =
|
|
370
|
-
const top2 =
|
|
423
|
+
const split2 = r2(w * (1 - fraction[0]));
|
|
424
|
+
const top2 = r2(h * HEADER_PORTRAIT);
|
|
371
425
|
return {
|
|
372
426
|
header: { x: m, y: m, w: w - 2 * m, h: top2 - 2 * m },
|
|
373
427
|
picture: { x: split2, y: top2, w: w - split2, h: h - top2 },
|
|
@@ -375,19 +429,19 @@ function side(w, h, fraction) {
|
|
|
375
429
|
alignX: 1
|
|
376
430
|
};
|
|
377
431
|
}
|
|
378
|
-
const split =
|
|
379
|
-
const top =
|
|
432
|
+
const split = r2(w * (1 - fraction[1]));
|
|
433
|
+
const top = r2(h * HEADER_LANDSCAPE);
|
|
380
434
|
return {
|
|
381
435
|
header: { x: m, y: m, w: split - 2 * m, h: top - m },
|
|
382
436
|
picture: { x: split, y: 0, w: w - split, h },
|
|
383
|
-
body: { x: m, y: top +
|
|
437
|
+
body: { x: m, y: top + r2(m / 2), w: split - 2 * m, h: h - top - r2(m / 2) - m },
|
|
384
438
|
alignX: 1
|
|
385
439
|
};
|
|
386
440
|
}
|
|
387
441
|
function bottom(w, h) {
|
|
388
|
-
const m =
|
|
389
|
-
const top =
|
|
390
|
-
const pic =
|
|
442
|
+
const m = r2(Math.min(w, h) * MARGIN);
|
|
443
|
+
const top = r2(h * BOTTOM_HEADER);
|
|
444
|
+
const pic = r2(h * BOTTOM_PICTURE);
|
|
391
445
|
return {
|
|
392
446
|
header: { x: m, y: m, w: w - 2 * m, h: top - 2 * m },
|
|
393
447
|
body: { x: m, y: top, w: w - 2 * m, h: pic - top - m },
|
|
@@ -395,8 +449,23 @@ function bottom(w, h) {
|
|
|
395
449
|
alignX: 0.5
|
|
396
450
|
};
|
|
397
451
|
}
|
|
452
|
+
function fromSpec(spec, w, h) {
|
|
453
|
+
return {
|
|
454
|
+
header: toRect(spec.header, w, h),
|
|
455
|
+
body: toRect(spec.body, w, h),
|
|
456
|
+
picture: toRect(spec.picture, w, h),
|
|
457
|
+
alignX: spec.alignX,
|
|
458
|
+
flanks: spec.flanks?.map((f) => toRect(f, w, h)),
|
|
459
|
+
logo: spec.logo ? toRect(spec.logo, w, h) : void 0,
|
|
460
|
+
arch: spec.arch
|
|
461
|
+
};
|
|
462
|
+
}
|
|
398
463
|
var mirror = (rect, w) => ({ ...rect, x: w - rect.x - rect.w });
|
|
399
464
|
function geometry(layout, w, h) {
|
|
465
|
+
const oriented = LAYOUT_SPECS[layout];
|
|
466
|
+
if (oriented) {
|
|
467
|
+
return fromSpec(h >= w ? oriented.portrait : oriented.landscape, w, h);
|
|
468
|
+
}
|
|
400
469
|
if (layout === "picture-bottom" /* PictureBottom */) {
|
|
401
470
|
return bottom(w, h);
|
|
402
471
|
}
|
|
@@ -433,6 +502,12 @@ function lineSize(line, width, k, avail) {
|
|
|
433
502
|
const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
|
|
434
503
|
return Math.max(0, Math.min(line.size * k, line.size * (avail - indent) / Math.max(width, 1)));
|
|
435
504
|
}
|
|
505
|
+
function groupRatio(m, k, avail) {
|
|
506
|
+
if (!m.group.uniform) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return Math.min(...m.group.lines.map((line, i) => lineSize(line, m.widths[i] ?? 1, k, avail) / line.size));
|
|
510
|
+
}
|
|
436
511
|
function placeGroup(m, k, col, y0, out) {
|
|
437
512
|
const { group } = m;
|
|
438
513
|
let y = y0;
|
|
@@ -443,11 +518,12 @@ function placeGroup(m, k, col, y0, out) {
|
|
|
443
518
|
const pad = group.card ? group.card.pad * k : 0;
|
|
444
519
|
const top = y;
|
|
445
520
|
y += pad;
|
|
521
|
+
const ratio = groupRatio(m, k, col.w - 2 * pad);
|
|
446
522
|
group.lines.forEach((line, i) => {
|
|
447
|
-
const size = lineSize(line, m.widths[i] ?? 1, k, col.w - 2 * pad);
|
|
523
|
+
const size = ratio === null ? lineSize(line, m.widths[i] ?? 1, k, col.w - 2 * pad) : line.size * ratio;
|
|
448
524
|
const indent = line.bullet ? line.size * k * BULLET_INDENT : 0;
|
|
449
|
-
const x = col.x + pad + indent;
|
|
450
525
|
const w = (m.widths[i] ?? 0) * size / line.size;
|
|
526
|
+
const x = col.right ? col.x + col.w - pad - w : col.x + pad + indent;
|
|
451
527
|
out?.texts.push({ text: line.text, font: "", color: line.color, box: { x, y, w, h: size * LINE_HEIGHT } });
|
|
452
528
|
if (line.bullet && out) {
|
|
453
529
|
out.bullets.push({ x: col.x + pad + size * BULLET_RADIUS, y: y + size * LINE_HEIGHT / 2, r: size * BULLET_RADIUS, color: line.bullet });
|
|
@@ -526,21 +602,22 @@ function flow(ctx, groups, zone, fonts) {
|
|
|
526
602
|
var BASE = 1080;
|
|
527
603
|
var LOGO_SHARE = 0.26;
|
|
528
604
|
var CARD_RADIUS = 0.035;
|
|
529
|
-
|
|
530
|
-
|
|
605
|
+
var CAP_TOP = 0.15;
|
|
606
|
+
function roundRect(ctx, r3, radius) {
|
|
607
|
+
const rad = Math.min(radius, r3.w / 2, r3.h / 2);
|
|
531
608
|
ctx.beginPath();
|
|
532
|
-
ctx.moveTo(
|
|
533
|
-
ctx.arcTo(
|
|
534
|
-
ctx.arcTo(
|
|
535
|
-
ctx.arcTo(
|
|
536
|
-
ctx.arcTo(
|
|
609
|
+
ctx.moveTo(r3.x + rad, r3.y);
|
|
610
|
+
ctx.arcTo(r3.x + r3.w, r3.y, r3.x + r3.w, r3.y + r3.h, rad);
|
|
611
|
+
ctx.arcTo(r3.x + r3.w, r3.y + r3.h, r3.x, r3.y + r3.h, rad);
|
|
612
|
+
ctx.arcTo(r3.x, r3.y + r3.h, r3.x, r3.y, rad);
|
|
613
|
+
ctx.arcTo(r3.x, r3.y, r3.x + r3.w, r3.y, rad);
|
|
537
614
|
ctx.closePath();
|
|
538
615
|
ctx.fill();
|
|
539
616
|
}
|
|
540
617
|
function drawFlow(ctx, f, radius) {
|
|
541
|
-
f.rules.forEach((
|
|
542
|
-
ctx.fillStyle =
|
|
543
|
-
ctx.fillRect(
|
|
618
|
+
f.rules.forEach((r3) => {
|
|
619
|
+
ctx.fillStyle = r3.color;
|
|
620
|
+
ctx.fillRect(r3.rect.x, r3.rect.y, r3.rect.w, r3.rect.h);
|
|
544
621
|
});
|
|
545
622
|
f.cards.forEach((c) => {
|
|
546
623
|
ctx.fillStyle = c.color;
|
|
@@ -571,6 +648,30 @@ function checked(input, assets) {
|
|
|
571
648
|
}
|
|
572
649
|
return result.doc;
|
|
573
650
|
}
|
|
651
|
+
function archRect(geo) {
|
|
652
|
+
if (geo.arch === void 0) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
const top = geo.picture.y + Math.round(geo.picture.h * geo.arch);
|
|
656
|
+
return { x: geo.picture.x, y: top, w: geo.picture.w, h: geo.picture.y + geo.picture.h - top };
|
|
657
|
+
}
|
|
658
|
+
function drawArch(ctx, r3, color) {
|
|
659
|
+
const rad = r3.w / 2;
|
|
660
|
+
ctx.fillStyle = color;
|
|
661
|
+
ctx.fillRect(r3.x, r3.y + rad, r3.w, r3.h - rad);
|
|
662
|
+
ctx.beginPath();
|
|
663
|
+
ctx.arc(r3.x + rad, r3.y + rad, rad, Math.PI, Math.PI * 2);
|
|
664
|
+
ctx.fill();
|
|
665
|
+
}
|
|
666
|
+
function flankFlows(ctx, groups, flanks, fonts) {
|
|
667
|
+
const half = Math.ceil(groups.length / 2);
|
|
668
|
+
return flanks.map((zone, i) => i === 0 ? flow(ctx, groups.slice(0, half), zone, fonts) : flow(ctx, groups.slice(half), { ...zone, right: true }, fonts));
|
|
669
|
+
}
|
|
670
|
+
function capAligned(zone, head) {
|
|
671
|
+
const first = head.texts[0]?.box;
|
|
672
|
+
const shift = first ? Math.min(Math.max(0, Math.round(first.y + first.h * CAP_TOP) - zone.y), Math.round(zone.h / 2)) : 0;
|
|
673
|
+
return { ...zone, y: zone.y + shift, h: zone.h - shift };
|
|
674
|
+
}
|
|
574
675
|
function splitHeader(header, hasLogo) {
|
|
575
676
|
const logoW = hasLogo ? Math.round(header.w * LOGO_SHARE) : 0;
|
|
576
677
|
return { text: { ...header, w: header.w - logoW * 1.1 }, logo: { ...header, x: header.x + header.w - logoW, w: logoW } };
|
|
@@ -593,20 +694,25 @@ function render(input, assets, canvas) {
|
|
|
593
694
|
ctx.fillRect(0, 0, width, height);
|
|
594
695
|
if (doc.background === "tint" /* Tint */) {
|
|
595
696
|
ctx.fillStyle = palette.tint;
|
|
596
|
-
[geo.header, geo.body].forEach((
|
|
697
|
+
[geo.header, geo.body].forEach((r3) => ctx.fillRect(r3.x, r3.y, r3.w, r3.h));
|
|
597
698
|
}
|
|
598
|
-
const zones = splitHeader(geo.header, doc.logo !== void 0);
|
|
699
|
+
const zones = geo.logo ? { text: geo.header, logo: geo.logo } : splitHeader(geo.header, doc.logo !== void 0);
|
|
599
700
|
const head = flow(ctx, headerGroups(doc, palette, u), zones.text, fonts);
|
|
600
|
-
const
|
|
701
|
+
const flanks = geo.flanks ?? [];
|
|
702
|
+
const body = flow(ctx, bodyGroups(doc, palette, u, flanks.length === 0), geo.body, fonts);
|
|
703
|
+
const sides = flanks.length > 0 ? flankFlows(ctx, flankPriceGroups(doc, palette, u), flanks, fonts) : [emptyFlow()];
|
|
601
704
|
const radius = Math.min(width, height) * CARD_RADIUS;
|
|
602
|
-
drawFlow(ctx,
|
|
603
|
-
drawFlow(ctx, body, radius);
|
|
705
|
+
[head, body, ...sides].forEach((f) => drawFlow(ctx, f, radius));
|
|
604
706
|
const logoImage = doc.logo ? assets[doc.logo.imageId] : void 0;
|
|
605
707
|
if (logoImage) {
|
|
606
|
-
const
|
|
607
|
-
ctx.drawImage(logoImage,
|
|
708
|
+
const r3 = placeContain(logoImage, capAligned(zones.logo, head), "top" /* Top */, 1, 1);
|
|
709
|
+
ctx.drawImage(logoImage, r3.x, r3.y, r3.w, r3.h);
|
|
608
710
|
}
|
|
609
711
|
const image = doc.picture ? assets[doc.picture.imageId] : void 0;
|
|
712
|
+
const backdrop = image ? archRect(geo) : null;
|
|
713
|
+
if (backdrop) {
|
|
714
|
+
drawArch(ctx, backdrop, palette.arch);
|
|
715
|
+
}
|
|
610
716
|
const picture = image && doc.picture ? placeContain(image, geo.picture, doc.picture.anchor, geo.alignX, doc.picture.scale) : null;
|
|
611
717
|
if (image && picture) {
|
|
612
718
|
ctx.drawImage(image, picture.x, picture.y, picture.w, picture.h);
|
|
@@ -616,8 +722,9 @@ function render(input, assets, canvas) {
|
|
|
616
722
|
height,
|
|
617
723
|
picture,
|
|
618
724
|
pictureRegion: geo.picture,
|
|
619
|
-
textBoxes: [
|
|
620
|
-
cardBoxes: body.cards.map((c) => c.rect)
|
|
725
|
+
textBoxes: [head, body, ...sides].flatMap((f) => f.texts.map((t) => t.box)),
|
|
726
|
+
cardBoxes: body.cards.map((c) => c.rect),
|
|
727
|
+
backdrop
|
|
621
728
|
};
|
|
622
729
|
}
|
|
623
730
|
|
|
@@ -636,6 +743,30 @@ function exportPng(canvas) {
|
|
|
636
743
|
return new Blob([bytes], { type: "image/png" });
|
|
637
744
|
}
|
|
638
745
|
|
|
746
|
+
// src/layoutCatalogue.ts
|
|
747
|
+
var POSTERS = ["story" /* Story */, "post" /* Post */, "square" /* Square */];
|
|
748
|
+
var LAYOUT_CATALOGUE = [
|
|
749
|
+
{ id: "picture-right" /* PictureRight */, designId: "E-A", label: "Picture right", description: "Titles across the top, details on the left, the picture on the right standing on the bottom edge.", formats: POSTERS },
|
|
750
|
+
{ id: "picture-left" /* PictureLeft */, designId: "E-B", label: "Picture left", description: "Titles across the top, the picture on the left standing on the bottom edge, details on the right.", formats: POSTERS },
|
|
751
|
+
{ id: "arch" /* Arch */, designId: "E-C", label: "Arch", description: "Titles and details on top, the picture centred in a soft arch at the bottom, prices either side.", formats: POSTERS },
|
|
752
|
+
{ id: "picture-bottom" /* PictureBottom */, designId: "picture-bottom", label: "Picture bottom", description: "Titles and details on top, the picture across the bottom half.", formats: POSTERS },
|
|
753
|
+
{ id: "list" /* List */, designId: "list", label: "Upcoming list", description: "Several dated events as a list beside the picture.", formats: POSTERS },
|
|
754
|
+
{ id: "phone-clock-safe" /* PhoneClockSafe */, designId: "P-A", label: "Phone, clock clear", description: "Lock-screen wallpaper: top kept clear for the clock, details on the left, picture on the right.", formats: ["phone" /* Phone */] },
|
|
755
|
+
{ id: "phone-arch" /* PhoneArch */, designId: "P-B", label: "Phone arch", description: "Lock-screen wallpaper: top kept clear for the clock, details in the middle, the picture in an arch at the bottom.", formats: ["phone" /* Phone */] },
|
|
756
|
+
{ id: "desktop-safe" /* DesktopSafe */, designId: "D-A", label: "Desktop, icons clear", description: "Desktop wallpaper: text on the left clear of desktop icons and the taskbar, the picture on the right.", formats: ["desktop" /* Desktop */] },
|
|
757
|
+
{ id: "desktop-arch" /* DesktopArch */, designId: "D-B", label: "Desktop arch", description: "Desktop wallpaper: titles left, the picture in a centred arch, details right.", formats: ["desktop" /* Desktop */] }
|
|
758
|
+
];
|
|
759
|
+
var DEFAULTS = {
|
|
760
|
+
["story" /* Story */]: "picture-right" /* PictureRight */,
|
|
761
|
+
["post" /* Post */]: "picture-right" /* PictureRight */,
|
|
762
|
+
["square" /* Square */]: "picture-right" /* PictureRight */,
|
|
763
|
+
["phone" /* Phone */]: "phone-clock-safe" /* PhoneClockSafe */,
|
|
764
|
+
["desktop" /* Desktop */]: "desktop-safe" /* DesktopSafe */
|
|
765
|
+
};
|
|
766
|
+
function defaultLayoutFor(format) {
|
|
767
|
+
return DEFAULTS[format];
|
|
768
|
+
}
|
|
769
|
+
|
|
639
770
|
// src/examples/kizombaWorkshop.ts
|
|
640
771
|
var WORKSHOPS = [
|
|
641
772
|
{ script: "Saturday 31 October", date: "Sat 31 Oct", first: "20:00 - 21:00", second: "21:00 - 22:00", party: "20\u20AC" },
|
|
@@ -693,10 +824,12 @@ exports.FORMAT_SIZES = FORMAT_SIZES;
|
|
|
693
824
|
exports.FlierDocError = FlierDocError;
|
|
694
825
|
exports.FontSetId = FontSetId;
|
|
695
826
|
exports.FormatId = FormatId;
|
|
827
|
+
exports.LAYOUT_CATALOGUE = LAYOUT_CATALOGUE;
|
|
696
828
|
exports.LayoutId = LayoutId;
|
|
697
829
|
exports.PALETTES = PALETTES;
|
|
698
830
|
exports.PaletteId = PaletteId;
|
|
699
831
|
exports.PictureAnchor = PictureAnchor;
|
|
832
|
+
exports.defaultLayoutFor = defaultLayoutFor;
|
|
700
833
|
exports.exportPng = exportPng;
|
|
701
834
|
exports.flierDocSchema = flierDocSchema;
|
|
702
835
|
exports.kizombaWorkshop = kizombaWorkshop;
|