@erclx/canon 4.35.0 → 4.36.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
CHANGED
package/src/slides/layouts.ts
CHANGED
|
@@ -30,6 +30,11 @@ export const LAYOUTS: LayoutInfo[] = [
|
|
|
30
30
|
{ name: 'stat-callout', description: 'Row of large stats with captions' },
|
|
31
31
|
{ name: 'grid', description: 'Two-by-two cards, each a term and detail' },
|
|
32
32
|
{ name: 'quote', description: 'Large pull quote with attribution' },
|
|
33
|
+
{
|
|
34
|
+
name: 'freeform',
|
|
35
|
+
description:
|
|
36
|
+
'Explicit shapes at a declared position and size, for a figure the other layouts cannot draw',
|
|
37
|
+
},
|
|
33
38
|
]
|
|
34
39
|
|
|
35
40
|
const LAYOUT_NAMES = new Set(LAYOUTS.map((layout) => layout.name))
|
|
@@ -55,6 +60,7 @@ const RENDERERS: Record<
|
|
|
55
60
|
'stat-callout': renderStatCallout,
|
|
56
61
|
grid: renderGrid,
|
|
57
62
|
quote: renderQuote,
|
|
63
|
+
freeform: renderFreeform,
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export function renderDeckSlide(
|
|
@@ -346,6 +352,81 @@ function renderQuote(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
|
346
352
|
}
|
|
347
353
|
}
|
|
348
354
|
|
|
355
|
+
const SHAPE_KEYS = new Set(['x', 'y', 'w', 'h', 'color'])
|
|
356
|
+
const COLOR_ROLES: (keyof Theme)[] = [
|
|
357
|
+
'background',
|
|
358
|
+
'surface',
|
|
359
|
+
'ink',
|
|
360
|
+
'muted',
|
|
361
|
+
'accent',
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
function renderFreeform(s: PptxSlide, slide: Slide, theme: Theme): void {
|
|
365
|
+
addTitle(s, slide.title, theme)
|
|
366
|
+
for (const line of slide.content) {
|
|
367
|
+
if (!line.trim().startsWith('-')) throw malformedShape(slide.title, line)
|
|
368
|
+
renderShapeLine(s, line, slide.title, theme)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function malformedShape(title: string, line: string): Error {
|
|
373
|
+
return new Error(
|
|
374
|
+
`Malformed freeform shape on slide "${title}": ${line.trim()}`,
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function renderShapeLine(
|
|
379
|
+
s: PptxSlide,
|
|
380
|
+
line: string,
|
|
381
|
+
title: string,
|
|
382
|
+
theme: Theme,
|
|
383
|
+
): void {
|
|
384
|
+
const stripped = line.replace(/^\s*-\s*/, '')
|
|
385
|
+
const colonIndex = stripped.indexOf(':')
|
|
386
|
+
const head = (
|
|
387
|
+
colonIndex === -1 ? stripped : stripped.slice(0, colonIndex)
|
|
388
|
+
).trim()
|
|
389
|
+
const text = colonIndex === -1 ? '' : stripped.slice(colonIndex + 1).trim()
|
|
390
|
+
|
|
391
|
+
const tokens = head.split(/\s+/).filter(Boolean)
|
|
392
|
+
const kind = tokens.shift()
|
|
393
|
+
if (kind !== 'text' && kind !== 'rect') throw malformedShape(title, line)
|
|
394
|
+
if (kind === 'text' && colonIndex === -1) throw malformedShape(title, line)
|
|
395
|
+
|
|
396
|
+
const attrs: Record<string, string> = {}
|
|
397
|
+
for (const token of tokens) {
|
|
398
|
+
const match = token.match(/^(\w+)=(.+)$/)
|
|
399
|
+
if (!match) throw malformedShape(title, line)
|
|
400
|
+
if (!SHAPE_KEYS.has(match[1])) throw malformedShape(title, line)
|
|
401
|
+
attrs[match[1]] = match[2]
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const x = Number(attrs.x)
|
|
405
|
+
const y = Number(attrs.y)
|
|
406
|
+
const w = Number(attrs.w)
|
|
407
|
+
const h = Number(attrs.h)
|
|
408
|
+
if (![x, y, w, h].every(Number.isFinite)) throw malformedShape(title, line)
|
|
409
|
+
|
|
410
|
+
const role = attrs.color as keyof Theme
|
|
411
|
+
if (!COLOR_ROLES.includes(role)) throw malformedShape(title, line)
|
|
412
|
+
const color = theme[role]
|
|
413
|
+
|
|
414
|
+
if (kind === 'text') {
|
|
415
|
+
s.addText(clean(text), {
|
|
416
|
+
x,
|
|
417
|
+
y,
|
|
418
|
+
w,
|
|
419
|
+
h,
|
|
420
|
+
fontFace: FONTS.body,
|
|
421
|
+
fontSize: TYPE.body,
|
|
422
|
+
color,
|
|
423
|
+
valign: 'top',
|
|
424
|
+
})
|
|
425
|
+
} else {
|
|
426
|
+
s.addShape('rect', { x, y, w, h, fill: { color } })
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
349
430
|
function addTitle(s: PptxSlide, title: string, theme: Theme): void {
|
|
350
431
|
if (!title) return
|
|
351
432
|
s.addText(clean(title), {
|