brand-intent 0.3.1 → 0.4.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/package.json +1 -1
- package/parser/compositionParser.ts +41 -41
- package/parser/formatParser.ts +6 -6
- package/parser/index.ts +2 -0
package/package.json
CHANGED
|
@@ -22,6 +22,26 @@ export interface ParsedImageRegion {
|
|
|
22
22
|
colEnd?: number; // 0-based column (undefined = full width)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Anchor keywords for illustration placement. */
|
|
26
|
+
export type IllustrationAnchor =
|
|
27
|
+
| 'top-left' | 'top-center' | 'top-right'
|
|
28
|
+
| 'center-left' | 'center' | 'center-right'
|
|
29
|
+
| 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
30
|
+
|
|
31
|
+
/** Parsed illustration placement from `illustration:` line. */
|
|
32
|
+
export interface ParsedIllustration {
|
|
33
|
+
anchor: IllustrationAnchor;
|
|
34
|
+
fit: 'width' | 'height';
|
|
35
|
+
/** Scale as fraction 0–1 (e.g. 0.9 = 90% of canvas dimension). */
|
|
36
|
+
scale: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ILLUSTRATION_ANCHORS = new Set<string>([
|
|
40
|
+
'top-left', 'top-center', 'top-right',
|
|
41
|
+
'center-left', 'center', 'center-right',
|
|
42
|
+
'bottom-left', 'bottom-center', 'bottom-right',
|
|
43
|
+
]);
|
|
44
|
+
|
|
25
45
|
export interface ParsedComposition {
|
|
26
46
|
/** Programmatic ID (from `id:` line, or fallback from first `#` line). */
|
|
27
47
|
id: string;
|
|
@@ -44,6 +64,8 @@ export interface ParsedComposition {
|
|
|
44
64
|
sticky: ParsedStickyRegion[];
|
|
45
65
|
/** Flow regions — content-driven flow areas. */
|
|
46
66
|
flow: ParsedFlowRegion | null;
|
|
67
|
+
/** Illustration placement (grid mode only for now). */
|
|
68
|
+
illustration?: ParsedIllustration;
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
/** A sticky (repetition) region — composed elements that don't participate in flow. */
|
|
@@ -128,8 +150,6 @@ export interface ParsedDivider {
|
|
|
128
150
|
|
|
129
151
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
130
152
|
|
|
131
|
-
/** Backward-compat map: band letters A-F → 0-based row indices */
|
|
132
|
-
const LEGACY_BAND_MAP: Record<string, number> = { A: 0, B: 1, C: 2, D: 3, E: 4, F: 5 };
|
|
133
153
|
const GRID_Y_KEYWORDS = new Set<string>(['top', 'center', 'bottom']);
|
|
134
154
|
|
|
135
155
|
const VALID_SLOTS = new Set<string>(['primary', 'secondary', 'detail', 'meta', 'cta', 'label']);
|
|
@@ -181,6 +201,7 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
181
201
|
const slots: ParsedSlot[] = [];
|
|
182
202
|
const sticky: ParsedStickyRegion[] = [];
|
|
183
203
|
let flow: ParsedFlowRegion | null = null;
|
|
204
|
+
let illustration: ParsedIllustration | undefined = undefined;
|
|
184
205
|
|
|
185
206
|
/** Parse a fraction string like "2/3" → 0.667. */
|
|
186
207
|
function parseFraction(s: string, ctx: string): number {
|
|
@@ -262,6 +283,23 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
262
283
|
continue;
|
|
263
284
|
}
|
|
264
285
|
|
|
286
|
+
// Illustration placement: "illustration: bottom-center fit-width 90%"
|
|
287
|
+
if (line.startsWith('illustration:')) {
|
|
288
|
+
const parts = line.slice(13).trim().split(/\s+/);
|
|
289
|
+
if (parts.length < 3) throw new Error(`${fileName}:${lineNum}: invalid illustration line — expected "illustration: <anchor> <fit-width|fit-height> <N%>"`);
|
|
290
|
+
const anchor = parts[0];
|
|
291
|
+
if (!ILLUSTRATION_ANCHORS.has(anchor)) throw new Error(`${fileName}:${lineNum}: invalid illustration anchor "${anchor}" (expected ${[...ILLUSTRATION_ANCHORS].join(', ')})`);
|
|
292
|
+
const fitStr = parts[1];
|
|
293
|
+
if (fitStr !== 'fit-width' && fitStr !== 'fit-height') throw new Error(`${fileName}:${lineNum}: invalid illustration fit "${fitStr}" (expected fit-width or fit-height)`);
|
|
294
|
+
const scaleStr = parts[2];
|
|
295
|
+
const scaleMatch = scaleStr.match(/^(\d+(?:\.\d+)?)%$/);
|
|
296
|
+
if (!scaleMatch) throw new Error(`${fileName}:${lineNum}: invalid illustration scale "${scaleStr}" (expected e.g. "90%")`);
|
|
297
|
+
const scale = parseFloat(scaleMatch[1]) / 100;
|
|
298
|
+
if (scale <= 0 || scale > 2) throw new Error(`${fileName}:${lineNum}: illustration scale out of range (expected 1%–200%)`);
|
|
299
|
+
illustration = { anchor: anchor as IllustrationAnchor, fit: fitStr === 'fit-width' ? 'width' : 'height', scale };
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
265
303
|
// grid: is no longer used in layout files (format owns the grid). Silently skip.
|
|
266
304
|
if (line.startsWith('grid:')) {
|
|
267
305
|
console.warn(`${fileName}:${lineNum}: "grid:" in layout files is no longer needed — format defines the grid`);
|
|
@@ -292,16 +330,6 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
292
330
|
logo = { row: parseInt(rowMatch[1], 10) - 1, col, size: (rowMatch[3] as 's' | 'm' | 'l') ?? undefined };
|
|
293
331
|
continue;
|
|
294
332
|
}
|
|
295
|
-
// Backward compat: "logo: band A [col] X"
|
|
296
|
-
const bandMatch = line.match(/^logo:\s+band\s+([A-F])\s+(?:col\s+)?(left|center|right)(?:\s+(s|m|l))?\s*$/);
|
|
297
|
-
if (bandMatch) {
|
|
298
|
-
const row = LEGACY_BAND_MAP[bandMatch[1]];
|
|
299
|
-
if (row === undefined) throw new Error(`${fileName}:${lineNum}: unknown band "${bandMatch[1]}"`);
|
|
300
|
-
console.warn(`${fileName}:${lineNum}: deprecated "band ${bandMatch[1]}" syntax — use "row ${row + 1}" instead`);
|
|
301
|
-
const col = resolveColKeyword(bandMatch[2], fileName, lineNum);
|
|
302
|
-
logo = { row, col, size: (bandMatch[3] as 's' | 'm' | 'l') ?? undefined };
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
333
|
throw new Error(`${fileName}:${lineNum}: invalid logo line "${line}" (expected "logo: row N left|center|right" or "logo: top/center/bottom left|center|right")`);
|
|
306
334
|
}
|
|
307
335
|
|
|
@@ -319,15 +347,6 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
319
347
|
icon = { row: parseInt(rowMatch[1], 10) - 1, col, size: (rowMatch[3] as 's' | 'm' | 'l') ?? undefined };
|
|
320
348
|
continue;
|
|
321
349
|
}
|
|
322
|
-
const bandMatch = line.match(/^icon:\s+band\s+([A-F])\s+(?:col\s+)?(left|center|right)(?:\s+(s|m|l))?\s*$/);
|
|
323
|
-
if (bandMatch) {
|
|
324
|
-
const row = LEGACY_BAND_MAP[bandMatch[1]];
|
|
325
|
-
if (row === undefined) throw new Error(`${fileName}:${lineNum}: unknown band "${bandMatch[1]}"`);
|
|
326
|
-
console.warn(`${fileName}:${lineNum}: deprecated "band ${bandMatch[1]}" syntax — use "row ${row + 1}" instead`);
|
|
327
|
-
const col = resolveColKeyword(bandMatch[2], fileName, lineNum);
|
|
328
|
-
icon = { row, col, size: (bandMatch[3] as 's' | 'm' | 'l') ?? undefined };
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
350
|
throw new Error(`${fileName}:${lineNum}: invalid icon line "${line}"`);
|
|
332
351
|
}
|
|
333
352
|
|
|
@@ -511,25 +530,6 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
511
530
|
continue;
|
|
512
531
|
}
|
|
513
532
|
|
|
514
|
-
// Backward compat: "primary band D cols 1--4 left"
|
|
515
|
-
const bandSlotMatch = line.match(/^(\w+)\s+band\s+([A-F])\s+cols\s+(\S+)\s+(left|center|right)$/);
|
|
516
|
-
if (bandSlotMatch) {
|
|
517
|
-
const [, slotId, bandLetter, colRange, align] = bandSlotMatch;
|
|
518
|
-
if (!VALID_SLOTS.has(slotId)) throw new Error(`${fileName}:${lineNum}: unknown slot "${slotId}"`);
|
|
519
|
-
const row = LEGACY_BAND_MAP[bandLetter];
|
|
520
|
-
if (row === undefined) throw new Error(`${fileName}:${lineNum}: unknown band "${bandLetter}"`);
|
|
521
|
-
console.warn(`${fileName}:${lineNum}: deprecated "band ${bandLetter}" syntax — use "row ${row + 1}" instead`);
|
|
522
|
-
const cols = parseColRange(colRange, fileName, lineNum, 12);
|
|
523
|
-
slots.push({
|
|
524
|
-
id: slotId as SlotId,
|
|
525
|
-
row,
|
|
526
|
-
colStart: cols.start,
|
|
527
|
-
colEnd: cols.end,
|
|
528
|
-
textAlign: align as 'left' | 'center' | 'right',
|
|
529
|
-
});
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
533
|
// Unknown line
|
|
534
534
|
throw new Error(`${fileName}:${lineNum}: unexpected "${line}"`);
|
|
535
535
|
}
|
|
@@ -552,5 +552,5 @@ export function parseCompositionFile(content: string, fileName: string): ParsedC
|
|
|
552
552
|
// Validate required fields
|
|
553
553
|
if (!image) image = 'full'; // default to full-bleed image
|
|
554
554
|
if (!logo && !logoNone && !sticky.length) throw new Error(`${fileName}: missing "logo:" line (or use sticky: block)`);
|
|
555
|
-
return { id, name, description, mode, image, imageExplicit, logo, logoNone: logoNone || undefined, icon, slots, sticky, flow };
|
|
555
|
+
return { id, name, description, mode, image, imageExplicit, logo, logoNone: logoNone || undefined, icon, slots, sticky, flow, illustration };
|
|
556
556
|
}
|
package/parser/formatParser.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface ParsedFormat {
|
|
|
29
29
|
dpi?: number;
|
|
30
30
|
bleedMm?: number;
|
|
31
31
|
// Optional grid override — absent means auto portrait/landscape selection
|
|
32
|
-
grid?: {
|
|
32
|
+
grid?: { rows: number; cols: number };
|
|
33
33
|
// Zones — always stored in px (mm values converted during parse)
|
|
34
34
|
danger: ZoneInsets;
|
|
35
35
|
gridCrop: ZoneInsets | null;
|
|
@@ -183,12 +183,12 @@ export function parseFormatFile(content: string, fileName: string): ParsedFormat
|
|
|
183
183
|
|
|
184
184
|
case 'grid': {
|
|
185
185
|
const parts = value.split(/\s+/);
|
|
186
|
-
if (parts.length !== 2) throw new Error(`${fileName}:${lineNum}: grid requires "
|
|
187
|
-
const
|
|
186
|
+
if (parts.length !== 2) throw new Error(`${fileName}:${lineNum}: grid requires "rows cols", got "${value}"`);
|
|
187
|
+
const rows = parseInt(parts[0], 10);
|
|
188
188
|
const cols = parseInt(parts[1], 10);
|
|
189
|
-
if (isNaN(
|
|
190
|
-
throw new Error(`${fileName}:${lineNum}: grid
|
|
191
|
-
grid = {
|
|
189
|
+
if (isNaN(rows) || rows < 1 || isNaN(cols) || cols < 1)
|
|
190
|
+
throw new Error(`${fileName}:${lineNum}: grid rows and cols must be positive integers`);
|
|
191
|
+
grid = { rows, cols };
|
|
192
192
|
break;
|
|
193
193
|
}
|
|
194
194
|
|