@erclx/canon 5.21.0 → 5.23.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.
Files changed (88) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +1 -1
  3. package/claude/skills/auto-ship/references/draft-mark.md +8 -6
  4. package/claude/skills/draft-doc/REQUIREMENT.md +45 -0
  5. package/claude/skills/draft-doc/SKILL.md +77 -0
  6. package/claude/skills/draft-doc/references/context.md +31 -0
  7. package/claude/skills/draft-doc/references/docs.md +33 -0
  8. package/claude/skills/draft-doc/references/readme.md +58 -0
  9. package/claude/skills/draft-doc/references/wiki.md +53 -0
  10. package/claude/skills/draft-doc/references/wireframes.md +48 -0
  11. package/claude/skills/draft-figure/REQUIREMENT.md +1 -1
  12. package/claude/skills/draft-figure/SKILL.md +1 -1
  13. package/claude/skills/git-pr/SKILL.md +5 -46
  14. package/claude/skills/git-pr/references/final-command.md +75 -0
  15. package/claude/skills/git-pr/references/labels.md +2 -2
  16. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +76 -0
  17. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -1
  18. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -1
  19. package/claude/skills/search-craft/REQUIREMENT.md +4 -4
  20. package/claude/skills/search-craft/SKILL.md +1 -1
  21. package/claude/skills/target-check/SKILL.md +9 -9
  22. package/claude/skills/target-check/references/domains.md +11 -5
  23. package/docs/agents/architecture-record.md +2 -2
  24. package/docs/agents/commands.md +1 -1
  25. package/docs/agents/drift-surfaces.md +20 -9
  26. package/docs/agents/index.md +3 -3
  27. package/docs/agents/install-and-sync.md +3 -3
  28. package/docs/agents/migrate.md +1 -1
  29. package/docs/agents/rule-citations.md +1 -1
  30. package/docs/agents/slides.md +47 -3
  31. package/docs/agents/tasks-validate.md +4 -2
  32. package/docs/agents/tasks.md +2 -2
  33. package/docs/target/migrations.md +3 -1
  34. package/docs/target/projects.md +1 -1
  35. package/docs/target/sync.md +1 -1
  36. package/docs/workflow/skill-map.md +14 -18
  37. package/governance/rules/canon/604-scratch.md +0 -1
  38. package/governance/rules/claude/575-hooks.md +1 -2
  39. package/governance/rules/claude/576-settings.md +1 -2
  40. package/governance/rules/standards/architecture.md +0 -1
  41. package/governance/rules/standards/context.md +0 -1
  42. package/governance/rules/standards/design.md +0 -1
  43. package/governance/rules/standards/diagrams.md +0 -3
  44. package/governance/rules/standards/requirements.md +0 -1
  45. package/governance/rules/standards/wireframes.md +0 -1
  46. package/package.json +1 -1
  47. package/scripts/manage-sandbox.sh +7 -8
  48. package/src/claude/cases/authoring.ts +5 -5
  49. package/src/commands/slides.ts +14 -4
  50. package/src/commands/sync.ts +10 -0
  51. package/src/commands/tasks.ts +16 -0
  52. package/src/gov/citations.ts +1 -2
  53. package/src/migrate/skill-names.ts +12 -0
  54. package/src/slides/convert/deck.ts +83 -3
  55. package/src/slides/convert/export.ts +67 -19
  56. package/src/slides/convert/shapes.ts +43 -3
  57. package/src/slides/convert/walk.ts +43 -1
  58. package/src/slides/package/fonts.ts +297 -0
  59. package/src/slides/package/motion.ts +316 -0
  60. package/src/slides/package/test-face.ts +113 -0
  61. package/src/sync/check.ts +9 -0
  62. package/src/sync/layout.ts +27 -1
  63. package/src/tasks/validate.ts +65 -0
  64. package/standards/architecture.md +2 -3
  65. package/standards/board.md +2 -0
  66. package/standards/context.md +0 -1
  67. package/standards/design.md +0 -1
  68. package/standards/diagrams.md +0 -3
  69. package/standards/requirements.md +3 -2
  70. package/standards/rule.md +1 -0
  71. package/standards/wireframes.md +0 -1
  72. package/tooling/claude/seeds/.claude/hooks/path-form.sh +2 -2
  73. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +10 -5
  74. package/tooling/claude/seeds/.claude/hooks/{tasks-index.sh → records-index.sh} +42 -35
  75. package/tooling/claude/seeds/.claude/settings.json +1 -5
  76. package/tooling/claude/seeds/canon/ARCHITECTURE.md +1 -1
  77. package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -0
  78. package/claude/skills/draft-context/REQUIREMENT.md +0 -37
  79. package/claude/skills/draft-context/SKILL.md +0 -61
  80. package/claude/skills/draft-docs/REQUIREMENT.md +0 -35
  81. package/claude/skills/draft-docs/SKILL.md +0 -59
  82. package/claude/skills/draft-readme/REQUIREMENT.md +0 -38
  83. package/claude/skills/draft-readme/SKILL.md +0 -73
  84. package/claude/skills/draft-wiki/REQUIREMENT.md +0 -42
  85. package/claude/skills/draft-wiki/SKILL.md +0 -83
  86. package/claude/skills/draft-wireframes/REQUIREMENT.md +0 -38
  87. package/claude/skills/draft-wireframes/SKILL.md +0 -69
  88. package/tooling/claude/seeds/.claude/hooks/memory-index.sh +0 -95
@@ -521,10 +521,44 @@ export interface Fallback {
521
521
  readonly properties: readonly UnmappedProperty[]
522
522
  }
523
523
 
524
+ /** A shape the slide will hold, by the record that produced it. */
525
+ export interface NamedShape {
526
+ readonly record: number
527
+ readonly name: string
528
+ }
529
+
524
530
  export interface SlidePlan {
525
531
  readonly ops: readonly DrawOp[]
526
532
  readonly fallbacks: readonly Fallback[]
527
533
  readonly refusedLinks: readonly RefusedLink[]
534
+ /** Every object name the ops carry, in the order they are drawn. */
535
+ readonly names: readonly NamedShape[]
536
+ }
537
+
538
+ /**
539
+ * pptxgenjs numbers shape ids itself, so the object name is the only handle a
540
+ * later pass over the written XML can find a shape by.
541
+ */
542
+ export const objectNameOf = (record: number): string => `canon-${record}`
543
+
544
+ /** An image's border is a second shape over it, named after the picture. */
545
+ function named<T extends DrawOp>(op: T, record: number): T {
546
+ const name = objectNameOf(record)
547
+ const options = { ...op.options, objectName: name }
548
+ if (op.kind === 'image' && op.frame) {
549
+ return {
550
+ ...op,
551
+ options,
552
+ frame: { ...op.frame, objectName: `${name}-frame` },
553
+ }
554
+ }
555
+ return { ...op, options }
556
+ }
557
+
558
+ function namesOf(record: number, op: DrawOp): NamedShape[] {
559
+ const names = [op.options.objectName]
560
+ if (op.kind === 'image') names.push(op.frame?.objectName)
561
+ return names.flatMap((name) => (name ? [{ record, name }] : []))
528
562
  }
529
563
 
530
564
  function boxOp(record: ElementRecord): DrawOp | undefined {
@@ -729,7 +763,13 @@ export function planSlide(
729
763
  const ops: DrawOp[] = []
730
764
  const fallbacks: Fallback[] = []
731
765
  const refusedLinks: RefusedLink[] = []
766
+ const names: NamedShape[] = []
732
767
  const dropped = new Set<number>()
768
+ const add = (record: number, op: DrawOp): void => {
769
+ const withName = named(op, record)
770
+ ops.push(withName)
771
+ names.push(...namesOf(record, withName))
772
+ }
733
773
 
734
774
  for (const record of records) {
735
775
  if (record.parent !== null && dropped.has(record.parent)) {
@@ -740,7 +780,7 @@ export function planSlide(
740
780
  if (properties.length > 0) {
741
781
  dropped.add(record.id)
742
782
  fallbacks.push({ id: record.id, selector: record.selector, properties })
743
- ops.push({
783
+ add(record.id, {
744
784
  kind: 'fallback',
745
785
  id: record.id,
746
786
  options: { ...position(record.bounds), altText: record.text },
@@ -757,8 +797,8 @@ export function planSlide(
757
797
  : record.kind === 'svg'
758
798
  ? svgOp(record)
759
799
  : tableOp(record, context, refusedLinks)
760
- if (op) ops.push(op)
800
+ if (op) add(record.id, op)
761
801
  }
762
802
 
763
- return { ops, fallbacks, refusedLinks }
803
+ return { ops, fallbacks, refusedLinks, names }
764
804
  }
@@ -1,5 +1,6 @@
1
1
  import type { ChartRecord } from '@/slides/convert/chart'
2
2
  import type { ThemeReading } from '@/slides/convert/master'
3
+ import type { TransitionSpec } from '@/slides/package/motion'
3
4
  import type {
4
5
  BoxStyle,
5
6
  ElementRecord,
@@ -31,6 +32,20 @@ export interface SlideMeta {
31
32
  readonly footer: BandOverride
32
33
  /** The `--color-text` the slide's `<body>` computes, as hex. */
33
34
  readonly textToken?: string
35
+ /** `data-transition` and `data-transition-duration` on `<body>`. */
36
+ readonly transition?: TransitionSpec
37
+ }
38
+
39
+ /**
40
+ * An element's `data-enter`, with the record it was walked as, or null when it
41
+ * was folded into another record's text or never drawn.
42
+ */
43
+ export interface EntranceRecord {
44
+ readonly record: number | null
45
+ readonly selector: string
46
+ readonly effect: string
47
+ readonly order?: string
48
+ readonly duration?: string
34
49
  }
35
50
 
36
51
  /** What one laid-out slide hands back across `page.evaluate`. */
@@ -38,6 +53,7 @@ export interface WalkedSlide {
38
53
  readonly background: Rgba
39
54
  readonly records: ElementRecord[]
40
55
  readonly charts: ChartRecord[]
56
+ readonly entrances: EntranceRecord[]
41
57
  readonly meta: SlideMeta
42
58
  }
43
59
 
@@ -532,6 +548,10 @@ export function walkSlide(idAttribute: string): WalkedSlide {
532
548
  : { kind: 'master' }
533
549
  }
534
550
  const section = body.getAttribute('data-section')?.trim()
551
+ const attribute = (element: Element, name: string): string | undefined =>
552
+ element.getAttribute(name)?.trim() || undefined
553
+ const transitionEffect = attribute(body, 'data-transition')
554
+ const transitionDuration = attribute(body, 'data-transition-duration')
535
555
  const textValue = getComputedStyle(body)
536
556
  .getPropertyValue('--color-text')
537
557
  .trim()
@@ -544,9 +564,31 @@ export function walkSlide(idAttribute: string): WalkedSlide {
544
564
  body.getAttribute('data-hidden') !== 'false',
545
565
  header: band('header'),
546
566
  footer: band('footer'),
567
+ ...(transitionEffect
568
+ ? {
569
+ transition: {
570
+ effect: transitionEffect,
571
+ ...(transitionDuration ? { duration: transitionDuration } : {}),
572
+ },
573
+ }
574
+ : {}),
547
575
  }
548
576
 
549
- return { background, records, charts, meta }
577
+ const entrances: EntranceRecord[] = Array.from(
578
+ body.querySelectorAll('[data-enter]'),
579
+ ).map((element) => {
580
+ const order = attribute(element, 'data-enter-order')
581
+ const duration = attribute(element, 'data-enter-duration')
582
+ return {
583
+ record: ids.get(element) ?? null,
584
+ selector: describe(element),
585
+ effect: attribute(element, 'data-enter') ?? '',
586
+ ...(order ? { order } : {}),
587
+ ...(duration ? { duration } : {}),
588
+ }
589
+ })
590
+
591
+ return { background, records, charts, entrances, meta }
550
592
  }
551
593
 
552
594
  /**
@@ -0,0 +1,297 @@
1
+ import type JSZip from 'jszip'
2
+
3
+ /**
4
+ * Embeds the deck's typeface in the written package, which pptxgenjs cannot do.
5
+ * PowerPoint reads an embedded face as an Embedded OpenType 2.2 container
6
+ * around the TrueType or OpenType data, stored as `ppt/fonts/fontN.fntdata`
7
+ * and listed in `presentation.xml`. The header is built from the face's own
8
+ * `OS/2`, `head`, and `name` tables, laid out per the W3C EOT submission:
9
+ * https://www.w3.org/submissions/EOT/
10
+ */
11
+
12
+ export interface EmbedFace {
13
+ readonly family: string
14
+ readonly weight: number
15
+ readonly style: 'normal' | 'italic'
16
+ /** As `deck.json` wrote it, which every notice names. */
17
+ readonly path: string
18
+ readonly bytes: Uint8Array
19
+ }
20
+
21
+ export interface FontNotice {
22
+ readonly path: string
23
+ readonly message: string
24
+ }
25
+
26
+ export type EotResult =
27
+ | { readonly status: 'embeddable'; readonly eot: Buffer }
28
+ | { readonly status: 'refused'; readonly message: string }
29
+
30
+ /** `fsType` bits from the OpenType `OS/2` table. */
31
+ const RESTRICTED = 0x0002
32
+ const BITMAP_ONLY = 0x0200
33
+
34
+ const SFNT_VERSIONS = new Set([0x00010000, 0x74727565, 0x4f54544f])
35
+ const WRAPPED: Readonly<Record<number, string>> = {
36
+ 0x774f4632: 'woff2',
37
+ 0x774f4646: 'woff',
38
+ 0x74746366: 'a font collection',
39
+ }
40
+
41
+ const UNDECODABLE = 'it could not be decoded as a TrueType or OpenType font'
42
+
43
+ interface Table {
44
+ readonly offset: number
45
+ readonly length: number
46
+ }
47
+
48
+ function tables(font: Buffer): Map<string, Table> {
49
+ const found = new Map<string, Table>()
50
+ const count = font.readUInt16BE(4)
51
+ for (let index = 0; index < count; index += 1) {
52
+ const at = 12 + index * 16
53
+ const offset = font.readUInt32BE(at + 8)
54
+ const length = font.readUInt32BE(at + 12)
55
+ if (offset + length > font.length) throw new RangeError('table overruns')
56
+ found.set(font.toString('latin1', at, at + 4), { offset, length })
57
+ }
58
+ return found
59
+ }
60
+
61
+ /**
62
+ * The Windows platform's strings, in UTF-16, preferring US English where a
63
+ * face carries several languages.
64
+ */
65
+ function names(font: Buffer, table: Table): Map<number, Buffer> {
66
+ const found = new Map<number, { language: number; bytes: Buffer }>()
67
+ const count = font.readUInt16BE(table.offset + 2)
68
+ const strings = table.offset + font.readUInt16BE(table.offset + 4)
69
+ for (let index = 0; index < count; index += 1) {
70
+ const at = table.offset + 6 + index * 12
71
+ if (font.readUInt16BE(at) !== 3) continue
72
+ const language = font.readUInt16BE(at + 4)
73
+ const id = font.readUInt16BE(at + 6)
74
+ const length = font.readUInt16BE(at + 8)
75
+ const start = strings + font.readUInt16BE(at + 10)
76
+ if (start + length > font.length) throw new RangeError('name overruns')
77
+ const held = found.get(id)
78
+ if (held && (held.language === 0x0409 || language !== 0x0409)) continue
79
+ const bytes = Buffer.from(font.subarray(start, start + length)).swap16()
80
+ found.set(id, { language, bytes })
81
+ }
82
+ return new Map([...found].map(([id, { bytes }]) => [id, bytes]))
83
+ }
84
+
85
+ function sized(bytes: Buffer = Buffer.alloc(0)): Buffer {
86
+ const size = Buffer.alloc(2)
87
+ size.writeUInt16LE(bytes.length)
88
+ return Buffer.concat([size, bytes])
89
+ }
90
+
91
+ function header(font: Buffer, os2: number, head: number): Buffer {
92
+ const fixed = Buffer.alloc(80)
93
+ fixed.writeUInt32LE(font.length, 4)
94
+ fixed.writeUInt32LE(0x00020002, 8)
95
+ font.copy(fixed, 16, os2 + 32, os2 + 42)
96
+ fixed.writeUInt8(1, 26)
97
+ fixed.writeUInt8(font.readUInt16BE(os2 + 62) & 1, 27)
98
+ fixed.writeUInt32LE(font.readUInt16BE(os2 + 4), 28)
99
+ fixed.writeUInt16LE(font.readUInt16BE(os2 + 8), 32)
100
+ fixed.writeUInt16LE(0x504c, 34)
101
+ for (let index = 0; index < 4; index += 1) {
102
+ fixed.writeUInt32LE(font.readUInt32BE(os2 + 42 + index * 4), 36 + index * 4)
103
+ }
104
+ for (let index = 0; index < 2; index += 1) {
105
+ fixed.writeUInt32LE(font.readUInt32BE(os2 + 78 + index * 4), 52 + index * 4)
106
+ }
107
+ fixed.writeUInt32LE(font.readUInt32BE(head + 8), 60)
108
+ return fixed
109
+ }
110
+
111
+ /**
112
+ * The EOT container around one face, or the reason it may not travel. A face
113
+ * whose license restricts embedding or allows a bitmap only is refused, as is
114
+ * a variable face, which PowerPoint does not embed. Editable and print-preview
115
+ * embedding both pass, since the deck is meant to be edited.
116
+ */
117
+ export function eotOf(bytes: Uint8Array): EotResult {
118
+ const font = Buffer.from(bytes)
119
+ const refused = (message: string): EotResult => ({
120
+ status: 'refused',
121
+ message,
122
+ })
123
+ if (font.length < 12) return refused(UNDECODABLE)
124
+ const version = font.readUInt32BE(0)
125
+ const wrapped = WRAPPED[version]
126
+ if (wrapped) {
127
+ return refused(
128
+ `it is ${wrapped}. Supply the TrueType or OpenType file instead`,
129
+ )
130
+ }
131
+ if (!SFNT_VERSIONS.has(version)) return refused(UNDECODABLE)
132
+ try {
133
+ const found = tables(font)
134
+ const os2 = found.get('OS/2')
135
+ const head = found.get('head')
136
+ const name = found.get('name')
137
+ // Version 0 of OS/2 stops before the code page ranges the header copies.
138
+ if (!os2 || os2.length < 86 || !head || head.length < 12 || !name) {
139
+ return refused(UNDECODABLE)
140
+ }
141
+ const fsType = font.readUInt16BE(os2.offset + 8)
142
+ if (fsType & RESTRICTED) {
143
+ return refused('its license forbids embedding (fsType restricted)')
144
+ }
145
+ if (fsType & BITMAP_ONLY) {
146
+ return refused('its license allows bitmap embedding only (fsType 0x0200)')
147
+ }
148
+ if (found.has('fvar')) {
149
+ return refused(
150
+ 'it is a variable font, which PowerPoint does not embed. Supply a static instance',
151
+ )
152
+ }
153
+ const strings = names(font, name)
154
+ const padding = Buffer.alloc(2)
155
+ // RootStringCheckSum, EUDCCodePage, Padding6, SignatureSize, EUDCFlags,
156
+ // and EUDCFontSize, all zero for a face with no root string or EUDC data.
157
+ const tail = Buffer.alloc(20)
158
+ const eot = Buffer.concat([
159
+ header(font, os2.offset, head.offset),
160
+ padding,
161
+ sized(strings.get(1)),
162
+ padding,
163
+ sized(strings.get(2)),
164
+ padding,
165
+ sized(strings.get(5)),
166
+ padding,
167
+ sized(strings.get(4)),
168
+ padding,
169
+ sized(),
170
+ tail,
171
+ font,
172
+ ])
173
+ eot.writeUInt32LE(eot.length, 0)
174
+ return { status: 'embeddable', eot }
175
+ } catch (error) {
176
+ if (error instanceof RangeError) return refused(UNDECODABLE)
177
+ throw error
178
+ }
179
+ }
180
+
181
+ type Slot = 'regular' | 'bold' | 'italic' | 'boldItalic'
182
+
183
+ /** The child order `<p:embeddedFont>` requires. */
184
+ const SLOTS: readonly Slot[] = ['regular', 'bold', 'italic', 'boldItalic']
185
+
186
+ /** PowerPoint keeps four faces a family, so a weight from 600 reads as bold. */
187
+ function slotOf(face: EmbedFace): Slot {
188
+ const isBold = face.weight >= 600
189
+ if (face.style === 'italic') return isBold ? 'boldItalic' : 'italic'
190
+ return isBold ? 'bold' : 'regular'
191
+ }
192
+
193
+ const FONT_RELATIONSHIP =
194
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/font'
195
+ const NOTES_SIZE = /<p:notesSz\b[^>]*\/>/
196
+
197
+ const escapeXml = (value: string): string =>
198
+ value
199
+ .replaceAll('&', '&amp;')
200
+ .replaceAll('"', '&quot;')
201
+ .replaceAll('<', '&lt;')
202
+
203
+ /**
204
+ * Embeds each face the deck lists and returns one notice per face left out.
205
+ * The font list goes after `<p:notesSz>`, where the schema orders it, and a
206
+ * package missing that anchor embeds nothing rather than placing it elsewhere.
207
+ */
208
+ export async function embedFonts(
209
+ zip: JSZip,
210
+ faces: readonly EmbedFace[],
211
+ ): Promise<FontNotice[]> {
212
+ const notices: FontNotice[] = []
213
+ const families = new Map<string, Map<Slot, number>>()
214
+ const parts: Buffer[] = []
215
+
216
+ for (const face of faces) {
217
+ const result = eotOf(face.bytes)
218
+ if (result.status === 'refused') {
219
+ notices.push({ path: face.path, message: result.message })
220
+ continue
221
+ }
222
+ const slots = families.get(face.family) ?? new Map<Slot, number>()
223
+ const slot = slotOf(face)
224
+ if (slots.has(slot)) {
225
+ notices.push({
226
+ path: face.path,
227
+ message: `${face.family} already embeds a ${slot} face, and PowerPoint holds one per slot`,
228
+ })
229
+ continue
230
+ }
231
+ parts.push(result.eot)
232
+ slots.set(slot, parts.length)
233
+ families.set(face.family, slots)
234
+ }
235
+ if (parts.length === 0) return notices
236
+
237
+ const presentationPath = 'ppt/presentation.xml'
238
+ const relsPath = 'ppt/_rels/presentation.xml.rels'
239
+ const typesPath = '[Content_Types].xml'
240
+ const [presentation, rels, types] = await Promise.all(
241
+ [presentationPath, relsPath, typesPath].map(
242
+ async (path) => (await zip.file(path)?.async('string')) ?? '',
243
+ ),
244
+ )
245
+ if (!presentation || !NOTES_SIZE.test(presentation)) {
246
+ const message = 'the deck has no <p:notesSz> to anchor the font list after'
247
+ return [
248
+ ...notices,
249
+ ...faces
250
+ .filter((face) => !notices.some((notice) => notice.path === face.path))
251
+ .map((face) => ({ path: face.path, message })),
252
+ ]
253
+ }
254
+
255
+ const id = (part: number): string => `rIdCanonFont${part}`
256
+ parts.forEach((eot, index) => {
257
+ zip.file(`ppt/fonts/font${index + 1}.fntdata`, eot)
258
+ })
259
+ const relationships = parts
260
+ .map(
261
+ (_, index) =>
262
+ `<Relationship Id="${id(index + 1)}" Type="${FONT_RELATIONSHIP}" Target="fonts/font${index + 1}.fntdata"/>`,
263
+ )
264
+ .join('')
265
+ zip.file(
266
+ relsPath,
267
+ rels.replace('</Relationships>', `${relationships}</Relationships>`),
268
+ )
269
+ if (!types.includes('Extension="fntdata"')) {
270
+ zip.file(
271
+ typesPath,
272
+ types.replace(
273
+ /(<Types\b[^>]*>)/,
274
+ '$1<Default Extension="fntdata" ContentType="application/x-fontdata"/>',
275
+ ),
276
+ )
277
+ }
278
+ const list = [...families]
279
+ .map(([family, slots]) => {
280
+ const children = SLOTS.flatMap((slot) => {
281
+ const part = slots.get(slot)
282
+ return part ? [`<p:${slot} r:id="${id(part)}"/>`] : []
283
+ })
284
+ return `<p:embeddedFont><p:font typeface="${escapeXml(family)}"/>${children.join('')}</p:embeddedFont>`
285
+ })
286
+ .join('')
287
+ zip.file(
288
+ presentationPath,
289
+ presentation
290
+ .replace(
291
+ NOTES_SIZE,
292
+ (anchor) => `${anchor}<p:embeddedFontLst>${list}</p:embeddedFontLst>`,
293
+ )
294
+ .replace('<p:presentation ', '<p:presentation embedTrueTypeFonts="1" '),
295
+ )
296
+ return notices
297
+ }