slotdeck 0.1.1 → 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.
Files changed (109) hide show
  1. package/README.md +94 -8
  2. package/dist/deck/alternatives.d.ts +34 -0
  3. package/dist/deck/alternatives.js +370 -0
  4. package/dist/deck/alternatives.js.map +1 -0
  5. package/dist/deck/design.d.ts +40 -0
  6. package/dist/deck/design.js +112 -0
  7. package/dist/deck/design.js.map +1 -0
  8. package/dist/deck/validate.d.ts +18 -0
  9. package/dist/deck/validate.js +44 -6
  10. package/dist/deck/validate.js.map +1 -1
  11. package/dist/index.d.ts +23 -1
  12. package/dist/index.js +14 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/llm/gemini.d.ts +4 -0
  15. package/dist/llm/gemini.js +43 -7
  16. package/dist/llm/gemini.js.map +1 -1
  17. package/dist/llm/provider.d.ts +12 -0
  18. package/dist/node.d.ts +7 -0
  19. package/dist/node.js +29 -0
  20. package/dist/node.js.map +1 -1
  21. package/dist/plan/catalog.d.ts +11 -1
  22. package/dist/plan/catalog.js +23 -8
  23. package/dist/plan/catalog.js.map +1 -1
  24. package/dist/plan/edit.js +14 -6
  25. package/dist/plan/edit.js.map +1 -1
  26. package/dist/plan/plan.d.ts +26 -2
  27. package/dist/plan/plan.js +58 -17
  28. package/dist/plan/plan.js.map +1 -1
  29. package/dist/plan/review.d.ts +43 -0
  30. package/dist/plan/review.js +141 -0
  31. package/dist/plan/review.js.map +1 -0
  32. package/dist/plan/write.d.ts +13 -1
  33. package/dist/plan/write.js +40 -7
  34. package/dist/plan/write.js.map +1 -1
  35. package/dist/preview/pagus.d.ts +30 -0
  36. package/dist/preview/pagus.js +45 -0
  37. package/dist/preview/pagus.js.map +1 -0
  38. package/dist/preview/patterns.d.ts +27 -0
  39. package/dist/preview/patterns.js +35 -0
  40. package/dist/preview/patterns.js.map +1 -0
  41. package/dist/preview/provider.d.ts +30 -0
  42. package/dist/preview/provider.js +11 -0
  43. package/dist/preview/provider.js.map +1 -0
  44. package/dist/render/render.d.ts +18 -1
  45. package/dist/render/render.js +32 -12
  46. package/dist/render/render.js.map +1 -1
  47. package/dist/render/repeat.d.ts +17 -2
  48. package/dist/render/repeat.js +150 -4
  49. package/dist/render/repeat.js.map +1 -1
  50. package/dist/render/text.d.ts +6 -0
  51. package/dist/render/text.js +24 -0
  52. package/dist/render/text.js.map +1 -1
  53. package/dist/template/budgets.d.ts +33 -0
  54. package/dist/template/budgets.js +38 -0
  55. package/dist/template/budgets.js.map +1 -0
  56. package/dist/template/change.d.ts +19 -1
  57. package/dist/template/change.js +45 -3
  58. package/dist/template/change.js.map +1 -1
  59. package/dist/template/check.d.ts +3 -0
  60. package/dist/template/check.js +53 -3
  61. package/dist/template/check.js.map +1 -1
  62. package/dist/template/derive.d.ts +54 -0
  63. package/dist/template/derive.js +402 -0
  64. package/dist/template/derive.js.map +1 -0
  65. package/dist/template/draft.d.ts +7 -0
  66. package/dist/template/draft.js +17 -1
  67. package/dist/template/draft.js.map +1 -1
  68. package/dist/template/instruct.js +15 -7
  69. package/dist/template/instruct.js.map +1 -1
  70. package/dist/template/manifest.d.ts +38 -0
  71. package/dist/template/manifest.js +43 -1
  72. package/dist/template/manifest.js.map +1 -1
  73. package/dist/template/sample.d.ts +17 -0
  74. package/dist/template/sample.js +31 -5
  75. package/dist/template/sample.js.map +1 -1
  76. package/dist/text/fontkit.d.ts +18 -0
  77. package/dist/text/fontkit.js +91 -0
  78. package/dist/text/fontkit.js.map +1 -0
  79. package/dist/text/fonts.d.ts +45 -0
  80. package/dist/text/fonts.js +59 -0
  81. package/dist/text/fonts.js.map +1 -0
  82. package/dist/text/layout.d.ts +12 -0
  83. package/dist/text/layout.js +104 -0
  84. package/dist/text/layout.js.map +1 -0
  85. package/dist/text/measure.d.ts +85 -0
  86. package/dist/text/measure.js +369 -0
  87. package/dist/text/measure.js.map +1 -0
  88. package/dist/text/styles.d.ts +123 -0
  89. package/dist/text/styles.js +417 -0
  90. package/dist/text/styles.js.map +1 -0
  91. package/dist/tools.d.ts +12 -0
  92. package/dist/tools.js +38 -6
  93. package/dist/tools.js.map +1 -1
  94. package/docs/README.md +16 -5
  95. package/docs/api-reference.md +291 -9
  96. package/docs/changelog.md +242 -0
  97. package/docs/chat-tools.md +26 -5
  98. package/docs/deck-format.md +6 -0
  99. package/docs/design-checks.md +216 -0
  100. package/docs/generating.md +43 -4
  101. package/docs/layout-freedom.md +278 -3
  102. package/docs/manifest-format.md +20 -3
  103. package/docs/preview.md +217 -0
  104. package/docs/providers.md +49 -4
  105. package/docs/rendering.md +8 -0
  106. package/docs/template-studio.md +121 -12
  107. package/docs/text-fit.md +456 -0
  108. package/docs/troubleshooting.md +21 -2
  109. package/package.json +7 -1
@@ -16,10 +16,11 @@ every field.
16
16
  ### `loadTemplate`
17
17
 
18
18
  ```ts
19
- function loadTemplate(pptx: Uint8Array, manifest: TemplateManifest): Template;
19
+ function loadTemplate(pptx: Uint8Array, manifest: TemplateManifest, options?: TemplateOptions): Template;
20
20
  ```
21
21
 
22
- Unzips a template once. Reuse the result.
22
+ Unzips a template once. Reuse the result. `options.fonts` gives it font metrics, which turns on text measuring
23
+ everywhere the template is used (see "Measuring text" below).
23
24
 
24
25
  ### `renderDeck`
25
26
 
@@ -45,6 +46,26 @@ function extractAssets(template: Template, deck: DeckSpec, pptx: Uint8Array): Re
45
46
 
46
47
  Reads a deck's pictures back from a pptx rendered from it, by asset id.
47
48
 
49
+ ### `slideAlternatives`
50
+
51
+ ```ts
52
+ function slideAlternatives(manifest: TemplateManifest, deck: DeckSpec, slide: number, options?: AlternativesOptions): SlideAlternative[];
53
+
54
+ interface AlternativesOptions { layout?: LayoutMode; patterns?: string[] }
55
+ interface SlideAlternative {
56
+ pattern: string; // a pattern that has a place for everything the slide says
57
+ description: string; // what that pattern is for
58
+ slide: SlideSpec; // the slide written for that pattern
59
+ changes: string[]; // what was done where the content was not copied slot for slot
60
+ dropped: string[]; // slots of the original whose photo or icon has no place
61
+ warnings: Issue[]; // what validateDeck says about the new slide
62
+ }
63
+ ```
64
+
65
+ The other patterns that can hold what slide number `slide` (from 1) of a deck says, with the slide written for
66
+ each. Synchronous, no model, no words changed. Throws a `RangeError` when the deck has no such slide. See
67
+ "Another layout for the same slide" in [layout-freedom.md](layout-freedom.md).
68
+
48
69
  ### `DeckValidationError`
49
70
 
50
71
  ```ts
@@ -66,13 +87,18 @@ Tell the kinds of slot value apart when walking a deck.
66
87
  ### Types
67
88
 
68
89
  ```ts
69
- interface Template { pkg: PptxPackage; manifest: TemplateManifest }
90
+ interface Template { pkg: PptxPackage; manifest: TemplateManifest; fonts?: FontMetrics }
91
+ interface TemplateOptions { fonts?: FontMetrics }
70
92
 
71
93
  interface RenderOptions { assets?: Record<string, Uint8Array>; layout?: LayoutMode }
72
- interface RenderResult { pptx: Uint8Array; warnings: Issue[] }
94
+ interface RenderResult {
95
+ pptx: Uint8Array;
96
+ warnings: Issue[];
97
+ shrunk: Array<{ slide: number; slot: string; size: number; scale: number }>; // texts set smaller to fit; empty without shrinkText
98
+ }
73
99
 
74
100
  type LayoutMode = 'strict' | 'adaptive';
75
- interface ValidateOptions { layout?: LayoutMode; assets?: Iterable<string> }
101
+ interface ValidateOptions { layout?: LayoutMode; assets?: Iterable<string>; measure?: TextMeasurer }
76
102
  interface Issue { severity: 'error' | 'warning'; slide?: number; slot?: string; message: string }
77
103
 
78
104
  interface DeckSpec {
@@ -172,6 +198,14 @@ function directionOf(language: string): Direction;
172
198
 
173
199
  The reading direction of a BCP 47 language tag.
174
200
 
201
+ ### `isPlainText`
202
+
203
+ ```ts
204
+ function isPlainText(pattern: PatternDef): boolean;
205
+ ```
206
+
207
+ Whether a slide made from the pattern is running text and nothing else: the patterns a text-only deck may use.
208
+
175
209
  ### `PlanError`
176
210
 
177
211
  ```ts
@@ -199,6 +233,7 @@ interface Brief {
199
233
  instructions?: string;
200
234
  fields?: Record<string, string>;
201
235
  textSize?: TextSize;
236
+ textOnly?: boolean; // every slide plain text: only the template's plain text patterns are offered
202
237
  }
203
238
  type ImageDensity = 'none' | 'low' | 'medium' | 'high';
204
239
 
@@ -210,6 +245,7 @@ interface PlanOptions {
210
245
  patterns?: string[];
211
246
  numberPatterns?: 'from-brief' | 'always' | 'never';
212
247
  maxRepairs?: number;
248
+ review?: DeckReview; // a second look at every draft; see "Design checks and reviews"
213
249
  onProgress?: (event: PlanProgress) => void;
214
250
  signal?: AbortSignal;
215
251
  }
@@ -287,6 +323,7 @@ const storeKeys: {
287
323
  deck(id: string): string; // 'deck/<id>.json'
288
324
  pptx(id: string): string; // 'pptx/<id>.pptx'
289
325
  asset(id: string): string; // 'asset/<id>'
326
+ preview(id: string, slide: number, extension: string): string; // 'preview/<id>/slide-03.svg'
290
327
  };
291
328
  ```
292
329
 
@@ -322,7 +359,9 @@ interface PresentationToolsOptions {
322
359
  layout?: LayoutMode;
323
360
  fields?: Record<string, string>;
324
361
  numberPatterns?: 'from-brief' | 'always' | 'never';
362
+ review?: DeckReview; // a second look at every draft the tools write
325
363
  thumbnail?: (patternId: string) => string | undefined;
364
+ preview?: PreviewProvider; // draws every presentation the tools create or change
326
365
  newId?: (kind: 'plan' | 'deck') => string;
327
366
  onProgress?: (event: GenerateProgress, tool: string) => void;
328
367
  }
@@ -331,7 +370,7 @@ interface PresentationTools {
331
370
  call(name: string, args: unknown, context?: { signal?: AbortSignal }): Promise<ToolResult>;
332
371
  }
333
372
  interface ToolDefinition { name: string; description: string; parameters: JsonSchema }
334
- interface ToolResult { output: Record<string, unknown>; plan?: DeckPlan; deck?: DeckSpec; pptx?: Uint8Array }
373
+ interface ToolResult { output: Record<string, unknown>; plan?: DeckPlan; deck?: DeckSpec; pptx?: Uint8Array; previews?: SlidePreview[] }
335
374
  ```
336
375
 
337
376
  ## Providers
@@ -366,6 +405,8 @@ interface JsonRequest {
366
405
  system: string;
367
406
  prompt: string;
368
407
  schema: JsonSchema;
408
+ maxOutputTokens?: number; // a ceiling on the answer's size, reasoning not counted; pass it on as an output limit
409
+ effort?: 'low'; // a short structural request: use light reasoning if the model has a setting for it
369
410
  images?: Array<{ mimeType: string; data: Uint8Array }>;
370
411
  signal?: AbortSignal;
371
412
  }
@@ -428,6 +469,17 @@ function changeManifest(template: Template, changes: ManifestChange[]): ChangeRe
428
469
  function editManifest(template: Template, instruction: string, options: ManifestEditOptions): Promise<ManifestEditResult>;
429
470
  ```
430
471
 
472
+ ### `patternVariants`, `VARIANTS`
473
+
474
+ ```ts
475
+ function patternVariants(template: Template, pattern: string): VariantOption[];
476
+ const VARIANTS: readonly ['mirror', 'text-only', 'without'];
477
+ type PatternVariant = (typeof VARIANTS)[number];
478
+ ```
479
+
480
+ The variants of a pattern that `changeManifest` can derive with `{ op: 'derivePattern' }`, each with whether
481
+ the pattern's slide allows it. See [template-studio.md](template-studio.md).
482
+
431
483
  ### `describeManifest`, `describeSlot`
432
484
 
433
485
  ```ts
@@ -482,6 +534,7 @@ interface DraftOptions {
482
534
  name?: string;
483
535
  slideImage?: (slide: number) => SlidePicture | undefined | Promise<SlidePicture | undefined>;
484
536
  slides?: number[];
537
+ variants?: Array<'mirror' | 'text-only'>; // also add these variants of every pattern that allows them
485
538
  concurrency?: number;
486
539
  onProgress?: (event: { done: number; total: number; slide: number }) => void;
487
540
  signal?: AbortSignal;
@@ -489,18 +542,19 @@ interface DraftOptions {
489
542
  interface DraftResult { manifest: TemplateManifest; skipped: Array<{ slide: number; reason: string }>; issues: ManifestIssue[] }
490
543
 
491
544
  interface ManifestIssue { severity: 'error' | 'warning'; pattern?: string; slot?: string; message: string }
492
- interface SampleOptions { fill?: number; items?: 'drawn' | 'max' | 'min' }
545
+ interface SampleOptions { fill?: number; items?: 'drawn' | 'max' | 'min'; fit?: 'budget' | 'box' }
493
546
 
494
547
  type ManifestChange =
495
- | { op: 'setManifest'; name?: string; language?: string; direction?: Direction; alignOnDirectionChange?: AlignPolicy }
548
+ | { op: 'setManifest'; name?: string; language?: string; direction?: Direction; alignOnDirectionChange?: AlignPolicy; textScale?: number | null }
496
549
  | { op: 'addPattern'; slide: number; id: string; description?: string }
497
550
  | { op: 'copyPattern'; pattern: string; id: string; description?: string }
551
+ | { op: 'derivePattern'; pattern: string; variant: PatternVariant; slot?: string; id?: string; description?: string }
498
552
  | { op: 'removePattern'; pattern: string }
499
553
  | { op: 'setPattern'; pattern: string; id?: string; description?: string; figures?: boolean }
500
554
  | { op: 'addSlot'; pattern: string; shape: string; role: Role; name?: string; optional?: boolean }
501
555
  | { op: 'removeSlot'; pattern: string; slot: string }
502
556
  | ({ op: 'setSlot'; pattern: string; slot: string } & SlotPatch)
503
- | { op: 'setRepeat'; pattern: string; min?: number; max?: number; item?: string; span?: { left: number; right: number } | null }
557
+ | { op: 'setRepeat'; pattern: string; min?: number; max?: number; item?: string; span?: { left: number; right: number } | null; widen?: number | null }
504
558
  | { op: 'removeRepeat'; pattern: string };
505
559
 
506
560
  interface SlotPatch {
@@ -536,6 +590,7 @@ interface ChangeResult {
536
590
  rejected: Array<{ index: number; change: ManifestChange; reason: string }>;
537
591
  issues: ManifestIssue[];
538
592
  }
593
+ interface VariantOption { variant: PatternVariant; slot?: string; available: boolean; note: string }
539
594
  interface ManifestEditOptions { llm: LlmProvider; signal?: AbortSignal }
540
595
  interface ManifestEditResult extends ChangeResult { note: string }
541
596
 
@@ -571,6 +626,230 @@ interface SlideInfo { number: number; part: string; layout: string; shapes: Shap
571
626
  interface TemplateInfo { slideWidth: number; slideHeight: number; defaultFontSize: number; slides: SlideInfo[] }
572
627
  ```
573
628
 
629
+ ## Design checks and reviews
630
+
631
+ Explained in [design-checks.md](design-checks.md). Both need a template loaded with fonts.
632
+
633
+ ### `checkDesign`
634
+
635
+ ```ts
636
+ function checkDesign(template: Template, deck: DeckSpec, options?: DesignOptions): DesignIssue[];
637
+ ```
638
+
639
+ Lists design problems that follow from measurement alone: text that runs over its box, boxes and cards left
640
+ mostly empty, headings that end in a lone word. Synchronous, no model, no picture.
641
+
642
+ ### `designReview`
643
+
644
+ ```ts
645
+ function designReview(template: Template, options?: DesignOptions): DeckReview;
646
+ ```
647
+
648
+ The design checks as a review for the planner's `review` option. Reports `empty`, `uneven` and `orphan`.
649
+
650
+ ### `visionReview`
651
+
652
+ ```ts
653
+ function visionReview(options: VisionReviewOptions): DeckReview;
654
+ ```
655
+
656
+ A review by a model that looks at pictures of the draft's slides, drawn by a `PreviewProvider` that gives
657
+ bitmap pictures. Not run against a live model; see the cautions in [design-checks.md](design-checks.md).
658
+
659
+ ### Types
660
+
661
+ ```ts
662
+ type DesignKind = 'overflow' | 'empty' | 'uneven' | 'orphan';
663
+ interface DesignIssue { severity: 'warning'; slide: number; slot: string; kind: DesignKind; message: string } // an Issue with a kind
664
+ interface DesignOptions {
665
+ layout?: LayoutMode;
666
+ kinds?: DesignKind[]; // default: all four
667
+ emptyBelow?: number; // default 1/3
668
+ headingSize?: number; // points; default 20
669
+ }
670
+
671
+ // A second look at a draft deck: problems to send back to the writing model.
672
+ type DeckReview = (deck: DeckSpec, context: { signal?: AbortSignal }) => Issue[] | Promise<Issue[]>;
673
+
674
+ interface VisionReviewOptions {
675
+ template: Template;
676
+ llm: LlmProvider; // a model that accepts pictures
677
+ preview: PreviewProvider; // must give PNG, JPEG, WebP or GIF
678
+ icons?: IconDef[];
679
+ assets?: AssetDef[];
680
+ layout?: LayoutMode; // default 'adaptive'
681
+ slidesPerRequest?: number; // default 6
682
+ }
683
+ ```
684
+
685
+ ## Slide previews
686
+
687
+ Explained in [preview.md](preview.md). slotdeck draws nothing itself: a `PreviewProvider` is a renderer you plug in.
688
+
689
+ ### `pagusPreviews`
690
+
691
+ ```ts
692
+ function pagusPreviews(core: PagusCore, renderer: PagusRenderer, options?: PagusOptions): PreviewProvider;
693
+
694
+ interface PagusCore { parse(data: ArrayBuffer): Promise<{ slides: readonly unknown[]; slideSize: unknown }> } // the `@pagus-kit/core` module
695
+ interface PagusRenderer { renderSlide(slide: never, slideSize: never, options?: never): { svg: string; width: number; height: number } } // the `@pagus-kit/renderer` module
696
+ interface PagusOptions { scale?: number; fontSubstitutes?: Record<string, string>; fontCss?: string }
697
+ ```
698
+
699
+ A provider that draws slides with the Pagus library, which you install and pass in:
700
+ `pagusPreviews(pagusCore, pagusRenderer)` after `import * as pagusCore from '@pagus-kit/core'` and
701
+ `import * as pagusRenderer from '@pagus-kit/renderer'`. One SVG for each slide, with the text as real text for a
702
+ browser to set. Paragraphs that start with a right-to-left letter are marked as running right to left.
703
+
704
+ ### `previewDeck`
705
+
706
+ ```ts
707
+ function previewDeck(template: Template, deck: DeckSpec, preview: PreviewProvider, options?: PreviewDeckOptions): Promise<{ pptx: Uint8Array; previews: SlidePreview[] }>;
708
+ ```
709
+
710
+ `renderDeck`, then the provider: the pptx and a picture of each of its slides.
711
+
712
+ ### `patternPreviews`
713
+
714
+ ```ts
715
+ function patternPreviews(template: Template, preview: PreviewProvider, options?: SampleOptions & { signal?: AbortSignal }): Promise<Record<string, SlidePreview>>;
716
+ ```
717
+
718
+ A picture of every pattern, by pattern id: each pattern's slide of the template's sample deck.
719
+
720
+ ### `previewExtension`
721
+
722
+ ```ts
723
+ function previewExtension(preview: { mimeType: string }): string; // 'svg', 'png', 'jpg', ...
724
+ ```
725
+
726
+ The file extension for a preview's type, for naming it in a store or on disk.
727
+
728
+ ### Types
729
+
730
+ ```ts
731
+ // What slotdeck needs from a slide renderer. Implement it to plug in any renderer.
732
+ interface PreviewProvider {
733
+ renderSlides(request: PreviewRequest): Promise<SlidePreview[]>; // one picture for each slide asked for, in slide order
734
+ }
735
+ interface PreviewRequest {
736
+ pptx: Uint8Array;
737
+ slides?: number[]; // 1-based; every slide when left out
738
+ signal?: AbortSignal;
739
+ }
740
+ interface SlidePreview {
741
+ slide: number; // 1-based
742
+ mimeType: string; // 'image/svg+xml', 'image/png'
743
+ data: Uint8Array; // SVG as UTF-8 text, or the bitmap's bytes
744
+ width?: number; // pixels
745
+ height?: number;
746
+ }
747
+ interface PreviewDeckOptions { assets?: Record<string, Uint8Array>; layout?: LayoutMode; signal?: AbortSignal }
748
+ ```
749
+
750
+ ## Measuring text
751
+
752
+ Explained in [text-fit.md](text-fit.md). All of it is optional: without fonts nothing here is used.
753
+
754
+ ### `fontkitMetrics`
755
+
756
+ ```ts
757
+ function fontkitMetrics(fontkit: FontkitModule, files: FontFiles): FontMetrics;
758
+
759
+ type FontFiles = Record<string, Uint8Array | Uint8Array[]>; // typeface name as the template states it -> its font files
760
+ interface FontkitModule { create(buffer: never, postscriptName?: string): unknown } // the `fontkit` module itself
761
+ ```
762
+
763
+ Font metrics from font files, read with the `fontkit` library, which you install and pass in:
764
+ `fontkitMetrics(fontkit, { Calibri: [regularBytes, boldBytes] })` after `import * as fontkit from 'fontkit'`.
765
+ Reads TrueType, OpenType, collections, WOFF and WOFF2.
766
+
767
+ ### `fontFiles` (from `slotdeck/node`)
768
+
769
+ ```ts
770
+ function fontFiles(fontkit: FontkitModule, directory: string, typefaces: Iterable<string>): FontFiles;
771
+ ```
772
+
773
+ Finds the font files of the given typefaces in a folder, such as the system's font folder, by the family name
774
+ each file states. Typefaces with no file there are left out.
775
+
776
+ ### `templateFonts`
777
+
778
+ ```ts
779
+ function templateFonts(template: Template, language?: string): TemplateFont[];
780
+ ```
781
+
782
+ The typefaces a template's text slots are set in, with the weights and slants used: the font files to supply.
783
+ `language` (default: the manifest's) adds the typeface that text of that language is set in.
784
+
785
+ ### `textMeasurer`
786
+
787
+ ```ts
788
+ function textMeasurer(template: Template, fonts?: FontMetrics): TextMeasurer;
789
+ ```
790
+
791
+ A measurer for a template. `fonts` defaults to the fonts the template was loaded with; it throws when there
792
+ are none. Pass the result to `validateDeck` as `measure`, or call `fit` yourself.
793
+
794
+ ### `measuredBudgets`
795
+
796
+ ```ts
797
+ function measuredBudgets(template: Template, options?: BudgetOptions): MeasuredBudget[];
798
+ ```
799
+
800
+ How many characters each text slot's box holds, as measured, with a budget to set. The template must have
801
+ fonts.
802
+
803
+ ### Types
804
+
805
+ ```ts
806
+ // What slotdeck needs from a font library. Implement it to use a library other than fontkit.
807
+ interface FontMetrics {
808
+ face(typeface: string, style: FontStyle): FontFace | undefined; // undefined: the typeface was not supplied
809
+ fallback?(codePoint: number, style: FontStyle): FontFace | undefined; // a face for a character the chosen one lacks
810
+ }
811
+ interface FontStyle { bold: boolean; italic: boolean }
812
+ interface FontFace {
813
+ has(codePoint: number): boolean;
814
+ advances(text: string, kerning: boolean): number[]; // the advance of each glyph after shaping, in ems
815
+ substitute?: boolean; // stands in for a weight or slant that was not supplied
816
+ }
817
+
818
+ interface TemplateFont { typeface: string; bold: boolean; italic: boolean }
819
+
820
+ interface TextMeasurer {
821
+ fit(pattern: string | PatternDef, slot: string | TextSlotDef, text: string | string[], options?: FitOptions): TextFit | undefined;
822
+ missingFonts(): TemplateFont[];
823
+ }
824
+ interface FitOptions {
825
+ textScale?: number; // the factor on body text the deck is rendered with; default 1
826
+ shrink?: ShrinkLimit; // allow the text to be set smaller when it does not fit
827
+ items?: number; // for a slot of a row of items: how many items the slide has
828
+ }
829
+ interface ShrinkLimit { to: number; minSize?: number } // the shape of the manifest's shrinkText
830
+ interface TextFit {
831
+ fits: boolean;
832
+ lines: number; // lines the text takes
833
+ capacity: number; // lines of this text the box holds
834
+ overflow: number; // lines that do not fit
835
+ height: number; // points the text takes
836
+ room: number; // points the box gives
837
+ widthRatio: number; // for a box that does not wrap: longest line / box width
838
+ size: number; // font size of the first paragraph, in points
839
+ sizes: number[]; // font size of each paragraph
840
+ scale: number; // the factor the text was shrunk by; 1 when it was not
841
+ fitsChars: number; // about how many characters of such text the box holds
842
+ paragraphs: string[][]; // the lines of each paragraph
843
+ widths: number[][]; // the width of each of those lines, in points
844
+ lineRoom: number[]; // the width a line of each paragraph may take, in points
845
+ sampleLines: number; // lines the template's own sample text takes in the slot; 0 when there is none to compare with
846
+ approximate: boolean; // a typeface, weight or outline was stood in for
847
+ }
848
+
849
+ interface BudgetOptions { sample?: string; margin?: number } // margin defaults to 0.9
850
+ interface MeasuredBudget { pattern: string; slot: string; maxChars?: number; fits: number; suggested: number }
851
+ ```
852
+
574
853
  ## Manifest types
575
854
 
576
855
  Described field by field in [manifest-format.md](manifest-format.md).
@@ -589,6 +868,8 @@ interface TemplateManifest {
589
868
  language?: string;
590
869
  direction: Direction;
591
870
  alignOnDirectionChange?: AlignPolicy;
871
+ textScale?: number; // enlarges body text in every deck made from the template
872
+ shrinkText?: { to: number; minSize?: number }; // lets text that does not fit be set smaller; needs fonts
592
873
  patterns: PatternDef[];
593
874
  }
594
875
  interface PatternDef {
@@ -608,6 +889,7 @@ interface RepeatDef {
608
889
  max?: number;
609
890
  span?: { left: number; right: number };
610
891
  columns?: number;
892
+ widen?: number; // fewer items on a line than drawn grow up to this factor to fill the row
611
893
  }
612
894
  type SlotDef = TextSlotDef | ImageSlotDef | TableSlotDef | ChartSlotDef;
613
895
 
@@ -0,0 +1,242 @@
1
+ # Changelog
2
+
3
+ What changed in each version, newest first, with what to do when upgrading. The version you have installed is
4
+ in `node_modules/slotdeck/package.json`.
5
+
6
+ ## 0.2.0
7
+
8
+ Two groups of additions. The first is about seeing and checking a deck: text measured with the template's
9
+ fonts, pictures of slides without PowerPoint, and design checks. The second is about more layouts from the
10
+ same template: variants the studio derives by itself, rows that fill their line, and other layouts for a
11
+ slide. Everything new is off until it is asked for.
12
+
13
+ ### Added
14
+
15
+ - **Text is measured with the template's fonts.** Load a template with font metrics
16
+ (`loadTemplate(pptx, manifest, { fonts })`) and text is broken into lines at each slot's typeface and size,
17
+ the way PowerPoint sets it. Validation and `renderDeck` then warn by the lines a text runs over
18
+ (`text takes 7 lines at 18 pt; the box holds 5 (2 lines too many). Text of about 290 characters fits.`), and
19
+ the planner sends a draft back to the model with that sentence. On the demo template every line broke at
20
+ the same word as in PowerPoint in all 1,153 text slots checked at the designed sizes, in English and in
21
+ Hebrew, and 699 of 700 slots had PowerPoint's line count with every font size halved. See
22
+ [text-fit.md](text-fit.md).
23
+ - **`fontkitMetrics(fontkit, files)`** builds the metrics from font files with the `fontkit` library, which
24
+ you install yourself: slotdeck does not depend on it. `FontMetrics` is the small interface behind it, for
25
+ using another library.
26
+ - **`templateFonts(template)`** lists the typefaces, weights and slants a template's text is set in, and
27
+ **`fontFiles(fontkit, folder, typefaces)`** from `slotdeck/node` finds their files in a font folder.
28
+ - **Shrinking text to fit.** `shrinkText: { to: 0.85, minSize: 10 }` on the manifest lets text that does not
29
+ fit be set smaller, in half points, down to the stated limit. `renderDeck` returns what it shrank in `shrunk`.
30
+ - **`textMeasurer(template)`** measures any text in any text slot: lines, capacity, overflow, the lines
31
+ themselves. `validateDeck` takes it as the `measure` option.
32
+ - **`measuredBudgets(template)`** says how many characters each text slot's box really holds and suggests a
33
+ budget, to set with `changeManifest`.
34
+ - **`checkManifest`** tries every text budget out when the template has fonts, and warns about a budget that
35
+ lets text run over its box. It also names typefaces that were not supplied.
36
+ - **`sampleDeck(template, { fit: 'box' })`** fills every text slot to what its box holds by measurement.
37
+ - **Slide previews.** `PreviewProvider` is a new interface for a renderer that turns a pptx into one picture
38
+ for each slide. slotdeck draws nothing itself and depends on no renderer. `pagusPreviews(core, renderer)` is
39
+ an adapter for the Pagus library, which you install yourself; it gives SVG for a browser to show, with
40
+ Hebrew and Arabic paragraphs marked right to left. `previewDeck` renders a deck and draws it,
41
+ `patternPreviews` makes a thumbnail for every pattern of a template, and `presentationTools` takes a
42
+ `preview` option: the pictures of every presentation it makes go to the store under `preview/<id>/` and to
43
+ the app in `ToolResult.previews`. No renderer tried reproduces PowerPoint exactly; what was found, for
44
+ English, Hebrew, tables and native charts, is in [preview.md](preview.md).
45
+ - **Design checks.** `checkDesign(template, deck)` lists problems of a deck that fits and still looks wrong,
46
+ from measurement alone: a box left almost empty, a card far shorter than its neighbours, a heading that ends
47
+ with one word on a line of its own. Every message says what length of text would put it right. See
48
+ [design-checks.md](design-checks.md).
49
+ - **Reviews.** The planner takes a `review` option: a second look at every draft, whose findings go back to
50
+ the writing model in the repair rounds. `designReview(template)` is the design checks as a review.
51
+ `visionReview({ template, llm, preview })` shows a model pictures of the slides; it needs a preview provider
52
+ that gives bitmap pictures, and it has been tested with a scripted model only, not run against a live one.
53
+ Nothing reviews a draft unless `review` is set. `presentationTools` takes the same option.
54
+ - **Variants the studio derives by itself.** A new `changeManifest` operation,
55
+ `{ op: 'derivePattern', pattern, variant }`, makes a second pattern from a pattern's slide and works out every
56
+ position and budget from the shapes of the slide. Variants: `mirror` (the picture and what sits beside it
57
+ change sides, including a photo that runs off the edge of the slide), `text-only` (the picture is left out
58
+ and the text widens over the room it leaves) and `without` (an optional text named in `slot` is left out and
59
+ what sits under it moves up). A variant the slide does not allow is refused with the reason. See "Variants
60
+ the code works out" in [layout-freedom.md](layout-freedom.md) and `derivePattern` in
61
+ [template-studio.md](template-studio.md).
62
+ - **`editManifest` understands requests for a variant**, such as "add a mirrored version of the photo slides".
63
+ The model names the variant; it still never sets a position.
64
+ - **`patternVariants(template, pattern)`** lists the variants a pattern allows, with the reason for each one it
65
+ does not, for a studio screen that offers them as buttons. `VARIANTS` is the list of variant names.
66
+ - **`variants` on `draftManifest`** adds the mirror image and the text-only version of every drafted pattern
67
+ that allows one. Off by default: see "Variants in a draft" in [template-studio.md](template-studio.md).
68
+ - **A slot can show a part of the design in another place.** A slot with `"empty": "keep"` and a `box` keeps
69
+ its shape's content and puts the shape at the box, and may point at any shape that is not inside a group,
70
+ a group as a whole included. `derivePattern` adds such slots (`moved1`, `moved2`, ...) for the shapes of a
71
+ variant that are not slots. See [manifest-format.md](manifest-format.md).
72
+ - **The demo template has mirrored photo patterns**: `text-and-photo-mirrored`, `photo-stat-mirrored` and
73
+ `list-with-photo-mirrored`, made with the new operation.
74
+ - **Rows that fill their line.** `widen` on a `repeat` lets the items of a row or grid grow wider when a line
75
+ holds fewer of them than the template draws, up to the factor given (`"widen": 1.5`), so that two or three
76
+ cards fill a row drawn for four. Backgrounds and text boxes widen; icons, pictures and circles keep their
77
+ size and their side of the item, also inside a group. The character budgets of the row's texts grow with
78
+ the items, in validation, in what the planner shows the model, and in `sampleDeck`. Set it by hand or with
79
+ `changeManifest(template, [{ op: 'setRepeat', pattern: '<id>', widen: 1.5 }])`; `editManifest` understands
80
+ requests such as "let two cards fill the row". See "Fewer items that fill the row" in
81
+ [layout-freedom.md](layout-freedom.md).
82
+ - **The demo template's `icon-cards` and `stat-cards` use `widen`**, so the example decks with three cards
83
+ or four figures fill their rows.
84
+ - **`slideAlternatives(manifest, deck, slide)`** lists the other patterns that can hold what one slide of a deck
85
+ says and returns the slide written for each: a row of cards as a list or as columns, a slide as its mirror
86
+ image, a list as paragraphs. Code only: no model call, and no word is changed. A photo or an icon the new
87
+ pattern has no place for is reported, and so is a text that is over its new budget. The chat tools do not
88
+ offer it yet. See "Another layout for the same slide" in [layout-freedom.md](layout-freedom.md).
89
+
90
+ - **Measuring follows the layout.** With fonts, the text of a row that widens its items is measured in the
91
+ box the renderer gives it at the slide's number of items, so validation, shrinking and the design checks
92
+ agree with the slide: text that fits two wide cards is accepted there and reported in four narrow ones. On
93
+ the demo template all 22 texts in widened cards and figures broke where PowerPoint broke them, in English
94
+ and in Hebrew. `checkManifest` tries the larger budgets of such a row in the wider boxes, and
95
+ `sampleDeck(template, { fit: 'box', items: 'min' })` fills them. A mirrored pattern, whose boxes only move
96
+ their shapes, is checked for empty boxes against the template's own text like the pattern it mirrors.
97
+
98
+ ### Changed
99
+
100
+ - `renderDeck` returns `shrunk` next to `pptx` and `warnings`. It is an empty array unless the manifest has
101
+ `shrinkText` and the template has fonts.
102
+ - `loadTemplate` takes a third, optional argument. `Template` has an optional `fonts` field.
103
+ - `storeKeys` has a `preview` key builder. With a `preview` provider the outputs of `create_presentation`
104
+ and `edit_presentation` gain `previews`, or `preview_error` when the pictures could not be made.
105
+ - **A slot with `"empty": "keep"` and a `box` now moves its shape.** Before, the `box` of such a slot was
106
+ ignored unless a deck gave the slot a value. `checkManifest` no longer reports that such a slot points at a
107
+ shape of another kind, or that it has no character budget.
108
+
109
+ ### Upgrading from 0.1.2
110
+
111
+ - A dependency range written for 0.1, such as `"slotdeck": "^0.1.2"`, does not take 0.2.0. Install it by
112
+ version: `npm install slotdeck@0.2.0`. The package itself has no new dependency; `fontkit` and the Pagus
113
+ packages are installed only by an app that wants measuring or previews.
114
+ - Nothing has to change. With a manifest made before and a template loaded without fonts, every result is the
115
+ same as in 0.1.2: character budgets, the same warnings, the same files. There is one exception: a manifest
116
+ that already had a slot with both `"empty": "keep"` and a `box` now shows that shape at the box. Remove the
117
+ `box` to keep the shape where the template drew it.
118
+ - To turn measuring on: `npm install fontkit`, build metrics with `fontkitMetrics`, and pass them to
119
+ `loadTemplate`. Code that builds a `Template` object by hand (`{ pkg, manifest }`) keeps working; add
120
+ `fonts` to it to measure.
121
+ - Once a template has fonts, a text longer than its `maxChars` that fits its box is no longer reported, and a
122
+ text within `maxChars` that does not fit is. Code that looks for the words `characters; the slot fits` in a
123
+ warning should also expect `lines`.
124
+ - Previews are off unless you pass a `preview` provider. To turn them on with Pagus:
125
+ `npm install @pagus-kit/core @pagus-kit/renderer` and `pagusPreviews(pagusCore, pagusRenderer)`.
126
+ - Design checks and reviews are off unless you call `checkDesign` or pass `review`. A review can make the
127
+ planner use more writing calls, since a draft within its limits may now be sent back.
128
+ - Budgets drafted without fonts are cautious. `measuredBudgets` usually suggests larger ones; setting them
129
+ lets the planning model write more per slide.
130
+ - To get mirrored layouts from your own template, run
131
+ `changeManifest(template, [{ op: 'derivePattern', pattern: '<id>', variant: 'mirror' }])` for each pattern with
132
+ a picture beside its text, render `sampleDeck` to look at the result, and store the new manifest.
133
+ `patternVariants(template, '<id>')` says which patterns allow it.
134
+ - A planner that is offered a pattern and its mirror image chooses between them by their descriptions. Give
135
+ each a description that says where the picture is.
136
+ - Rows and grids keep the size of their items unless the manifest opts in with `widen`, and decks made from
137
+ a manifest without it render byte for byte as before. After opting in, a slide with fewer items than drawn
138
+ looks different (its items are wider) and its text budgets are larger; text that fitted before still fits.
139
+ Render `sampleDeck(template, { items: 'min' })` to look at it before using the manifest.
140
+
141
+ ## 0.1.2
142
+
143
+ ### Added
144
+
145
+ - **Text-only decks.** `textOnly: true` on a brief, or `text_only: true` in the `plan_presentation` tool, plans
146
+ every slide as plain text: a title with paragraphs or a list, and no pictures, icons, cards, figures, tables
147
+ or charts. The planner is then offered only the template's plain text patterns, so no other kind can be
148
+ chosen. See "Asking for a kind of slide" in [generating.md](generating.md).
149
+ - **Wishes about the kind of slides are honoured.** A wish stated in words in the brief or in an edit
150
+ instruction ("text only", "no tables", "only cards") now decides which patterns may be used and comes before
151
+ variety. It works when planning, when changing a plan, and when changing a finished deck. The pattern list
152
+ the model reads marks each plain text pattern with "Plain text.".
153
+ - **`isPlainText(pattern)`** tells whether a pattern is plain text, for an app that wants to show or check
154
+ which layouts a text-only deck can use.
155
+ - **Larger body text for a whole template.** `textScale` on the manifest enlarges the body text of every deck
156
+ made from the template (`1.3` turns 9 pt into about 12 pt). Budgets shrink to match in validation, in what
157
+ the planner shows the model, and in `sampleDeck`. Set it by hand or with
158
+ `changeManifest(template, [{ op: 'setManifest', textScale: 1.3 }])`; `editManifest` understands requests such
159
+ as "make body text 20 percent larger". See "Larger text for a whole template" in
160
+ [layout-freedom.md](layout-freedom.md).
161
+ - **`maxOutputTokens` on requests to the model.** The planner passes a ceiling for each answer with every
162
+ request it makes. See "Runaway answers" in [providers.md](providers.md).
163
+ - **`effort: 'low'` on planning requests.** Calls that choose and arrange rather than write (the outline, a
164
+ change to a plan, the first step of an edit) ask for light reasoning.
165
+
166
+ ### Changed
167
+
168
+ - **Table text and chart labels follow the text scale**, where the manifest states their size
169
+ (`bodyText.size`, `headerText.size`, `text.size`). This applies to a deck's own text size (`textScale`,
170
+ `textSize`, `text_size`) as well as to the new template scale, and a table cell then holds fewer characters.
171
+ Before, tables and charts kept their size when a deck asked for larger text.
172
+ - **The Gemini adapter limits the output of every request.** The limit is the request's estimate plus 8,192
173
+ tokens for reasoning, or 24,576 when the request has none, or the adapter's `maxOutputTokens` when set.
174
+ Before, a request had no limit unless `maxOutputTokens` was set.
175
+ - **The Gemini adapter sends a request once more** when the answer was cut off at the limit, after a server
176
+ error (500 to 504), and when the service's recitation filter blocks an ordinary answer. A bad key, a quota or
177
+ a bad request is not repeated.
178
+ - **The Gemini adapter uses thinking level `low` for requests with `effort: 'low'`**, unless `thinkingLevel`
179
+ is set on the adapter. Planning calls became several times faster: a plan takes about 5 seconds.
180
+ - The per-slide sentence of a plan is asked for as "a single sentence of at most 25 words".
181
+
182
+ ### Fixed
183
+
184
+ - **A request for a text-only presentation gave cards and tables.** The planner was told to vary the layouts
185
+ and nothing said that a wish about the kind of slides comes first; and "no pictures" was the most that
186
+ reached it. Fixed by the two additions above.
187
+ - **A model call could hang for minutes.** With its default reasoning the model sometimes ran away inside one
188
+ text field of a planning answer and wrote tens of thousands of tokens. Measured on a text-only plan: 6 of 24
189
+ calls with default reasoning, none of 18 with light reasoning. Planning calls now use light reasoning, every
190
+ call has an output limit, and a cut-off answer is asked for once more.
191
+
192
+ ### Upgrading from 0.1.1
193
+
194
+ No code has to change. To benefit:
195
+
196
+ - **Chat assistants:** `plan_presentation` has a new optional argument, `text_only`. It is in
197
+ `tools.definitions`, so it reaches the chat model when the definitions are passed as before. If your system
198
+ prompt lists the tool's arguments, add it, and tell the model that "no pictures" (`image_density: "none"`)
199
+ still allows cards, tables and charts.
200
+ - **Small body text:** if decks from your template have body text that is too small, add `"textScale": 1.3`
201
+ (or another factor) to the manifest, render `sampleDeck` once to see that everything still fits, and keep the
202
+ manifest. A manifest with `textScale` is ignored by 0.1.1 and earlier, which show the designed size.
203
+ - **Your own `LlmProvider`:** requests now carry two optional fields, `maxOutputTokens` and `effort`. A provider
204
+ that ignores them keeps working. To get the protection and the speed, pass `maxOutputTokens` on as your
205
+ model's output limit (adding room for reasoning tokens if your model counts them in the same limit), use
206
+ light reasoning when `effort` is `'low'`, and retry once when an answer is cut off. See "Runaway answers" in
207
+ [providers.md](providers.md).
208
+ - **Tables with larger text:** a deck that asks for larger text now enlarges table text too, where the
209
+ manifest states its size, and gives cells a smaller budget. A deck stored with `textScale` renders with
210
+ larger table text than before.
211
+
212
+ ## 0.1.1
213
+
214
+ ### Added
215
+
216
+ - **Documentation inside the package.** The `docs/` folder ships with the npm package
217
+ (`node_modules/slotdeck/docs/`), in plain Markdown with no links to the internet, so it can be read where
218
+ there is no network access. Start at [README.md](README.md).
219
+
220
+ ### Changed
221
+
222
+ - **Unknown chart format.** `validateDeck` warns about a chart `format` other than `"number"` or `"percent"`
223
+ (`chart format "x" is not known and is ignored`). Before, it was ignored silently.
224
+
225
+ ### Upgrading from 0.1.0
226
+
227
+ Nothing to change.
228
+
229
+ ## 0.1.0
230
+
231
+ First release.
232
+
233
+ - **Renderer:** `loadTemplate`, `renderDeck`, `validateDeck`, `extractAssets`. Text, pictures, tables and
234
+ native charts; rows and grids with a variable number of items; larger text on request; derived patterns.
235
+ - **Planner and editor:** `generateDeck`, `planDeck`, `planOutline`, `editPlan`, `writeDeck`, `editDeck`,
236
+ `planEdit`, `resolveAssets`, with any model through `LlmProvider` and `ImageProvider`.
237
+ - **Chat tools:** `presentationTools` with `plan_presentation`, `edit_presentation_plan`,
238
+ `create_presentation` and `edit_presentation`, and stores (`memoryStore`, `directoryStore`).
239
+ - **Gemini adapters:** `gemini`, `geminiImages`, `vertexEndpoint`.
240
+ - **Reading direction:** right-to-left decks on left-to-right templates and the reverse.
241
+ - **Template studio:** `draftManifest`, `checkManifest`, `sampleDeck`, `changeManifest`, `editManifest`,
242
+ `studioView`, `describeManifest`, `inspectTemplate`.