@namahapdf/pptx 0.1.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.
@@ -0,0 +1,897 @@
1
+ /**
2
+ * Resolved presentation model produced by PptxParser and consumed by
3
+ * SlideRenderer / pdfBuilder. Geometry is normalized to **PDF points**
4
+ * (top-left origin) and colors are fully resolved to RGBA (theme lookups and
5
+ * lumMod/shade/tint/alpha transforms already applied).
6
+ */
7
+ /** 0..255 channels, alpha 0..1. */
8
+ type Rgba = {
9
+ r: number;
10
+ g: number;
11
+ b: number;
12
+ a: number;
13
+ };
14
+ type GradientStop = {
15
+ pos: number;
16
+ color: Rgba;
17
+ };
18
+ type Fill = {
19
+ kind: 'none';
20
+ } | {
21
+ kind: 'solid';
22
+ color: Rgba;
23
+ } | {
24
+ kind: 'gradient';
25
+ stops: GradientStop[];
26
+ angleDeg: number;
27
+ radial: boolean;
28
+ } | {
29
+ kind: 'image';
30
+ dataUrl: string;
31
+ alpha: number;
32
+ };
33
+ type Stroke = {
34
+ fill: Fill;
35
+ widthPt: number;
36
+ dash: number[];
37
+ cap: 'butt' | 'round' | 'square';
38
+ };
39
+ /** Box in PDF points, top-left origin, plus rotation/flip about the box center. */
40
+ type Transform = {
41
+ xPt: number;
42
+ yPt: number;
43
+ wPt: number;
44
+ hPt: number;
45
+ rotDeg: number;
46
+ flipH: boolean;
47
+ flipV: boolean;
48
+ };
49
+ type Geometry = {
50
+ /** DrawingML preset name (e.g. 'rect', 'roundRect', 'ellipse'); '' for custom. */
51
+ preset: string;
52
+ /** Custom geometry path segments in EMU within the shape box (Phase 2+). */
53
+ custom?: PathSeg[];
54
+ /** Adjust values (a:avLst) by name, e.g. 'adj' → fraction. */
55
+ adjust?: Record<string, number>;
56
+ };
57
+ type PathSeg = {
58
+ c: 'M';
59
+ x: number;
60
+ y: number;
61
+ } | {
62
+ c: 'L';
63
+ x: number;
64
+ y: number;
65
+ } | {
66
+ c: 'C';
67
+ x1: number;
68
+ y1: number;
69
+ x2: number;
70
+ y2: number;
71
+ x: number;
72
+ y: number;
73
+ } | {
74
+ c: 'Z';
75
+ };
76
+ type Run = {
77
+ text: string;
78
+ fontSizePt: number;
79
+ bold: boolean;
80
+ italic: boolean;
81
+ underline: boolean;
82
+ color: Rgba;
83
+ /** Resolved typeface family name (theme +mj-lt/+mn-lt already resolved). */
84
+ typeface: string;
85
+ /** Hyperlink target (a:hlinkClick), resolved via slide rels. Semantic only. */
86
+ hyperlink?: string;
87
+ };
88
+ type BulletKind = 'none' | 'char' | 'number';
89
+ type Paragraph = {
90
+ runs: Run[];
91
+ align: 'l' | 'ctr' | 'r' | 'just';
92
+ /** Indent level 0..8. */
93
+ level: number;
94
+ bullet: {
95
+ kind: BulletKind;
96
+ char?: string;
97
+ color?: Rgba;
98
+ numType?: string;
99
+ startAt?: number;
100
+ };
101
+ /** Left margin / first-line indent, points. */
102
+ marLPt: number;
103
+ indentPt: number;
104
+ /** Line spacing: multiple of line height when `linePct`, else exact points. */
105
+ linePct?: number;
106
+ lineExactPt?: number;
107
+ spaceBeforePt: number;
108
+ spaceAfterPt: number;
109
+ /** Fallback run props for empty paragraphs (end-paragraph run props). */
110
+ defaultSizePt: number;
111
+ defaultColor: Rgba;
112
+ defaultTypeface: string;
113
+ };
114
+ type TextBody = {
115
+ paragraphs: Paragraph[];
116
+ anchor: 't' | 'ctr' | 'b';
117
+ insL: number;
118
+ insT: number;
119
+ insR: number;
120
+ insB: number;
121
+ wrap: boolean;
122
+ /** normAutofit font scale (1 = none). */
123
+ fontScale: number;
124
+ /** normAutofit line-spacing reduction as a fraction (0 = none). */
125
+ lnSpcReduction: number;
126
+ /**
127
+ * Autofit mode: 'norm' shrinks text to fit the box (we compute the scale
128
+ * ourselves when PowerPoint didn't persist one), 'shape' means the shape was
129
+ * sized to the text (no shrink), 'none' clips overflow.
130
+ */
131
+ autofit: 'norm' | 'shape' | 'none';
132
+ };
133
+ type ShapeNode = ShapeEl | PictureEl | GroupEl | GraphicFrameEl;
134
+ /** Semantic metadata carried alongside the render model (for structure export). */
135
+ type ShapeMeta = {
136
+ /** cNvPr id — the stable shape locator inside its part (PptxAnchor.shapeId). */
137
+ id?: string;
138
+ /** cNvPr name, e.g. "Title 1". */
139
+ name?: string;
140
+ /** Placeholder type (title/ctrTitle/subTitle/body/...) when this is a placeholder. */
141
+ phType?: string;
142
+ /** Placeholder index. */
143
+ phIdx?: string;
144
+ /** Alt text (cNvPr descr/title). */
145
+ altText?: string;
146
+ /** Which part the shape came from — only 'slide' shapes are editable in place. */
147
+ source?: 'slide' | 'layout' | 'master';
148
+ };
149
+ /** Outer drop shadow (a:outerShdw). Offsets/blur in points. */
150
+ type Shadow = {
151
+ color: Rgba;
152
+ blurPt: number;
153
+ offsetXPt: number;
154
+ offsetYPt: number;
155
+ };
156
+ /** Source-rect crop as edge fractions (0..1) of the image (a:srcRect). */
157
+ type SrcRect = {
158
+ l: number;
159
+ t: number;
160
+ r: number;
161
+ b: number;
162
+ };
163
+ type ShapeEl = {
164
+ kind: 'shape';
165
+ transform: Transform;
166
+ geometry: Geometry;
167
+ fill: Fill;
168
+ stroke: Stroke | null;
169
+ textBody: TextBody | null;
170
+ shadow: Shadow | null;
171
+ meta: ShapeMeta;
172
+ };
173
+ type PictureEl = {
174
+ kind: 'picture';
175
+ transform: Transform;
176
+ dataUrl: string | null;
177
+ /** Stable id for the underlying media (rel target), for enrichment/dedupe. */
178
+ assetId?: string;
179
+ /** Optional outline. */
180
+ stroke: Stroke | null;
181
+ shadow: Shadow | null;
182
+ /** Crop applied to the source image, if any. */
183
+ srcRect: SrcRect | null;
184
+ meta: ShapeMeta;
185
+ };
186
+ type GroupEl = {
187
+ kind: 'group';
188
+ transform: Transform;
189
+ /** Child coordinate space (chOff/chExt) → lets us map child EMU into the group box. */
190
+ chOffXEmu: number;
191
+ chOffYEmu: number;
192
+ chExtWEmu: number;
193
+ chExtHEmu: number;
194
+ children: ShapeNode[];
195
+ };
196
+ type TableCell = {
197
+ rowSpan: number;
198
+ gridSpan: number;
199
+ /** True when covered by a merge (skip drawing). */
200
+ merged: boolean;
201
+ fill: Fill;
202
+ borders: {
203
+ l?: Stroke;
204
+ t?: Stroke;
205
+ r?: Stroke;
206
+ b?: Stroke;
207
+ };
208
+ textBody: TextBody | null;
209
+ };
210
+ type Table = {
211
+ colWidthsPt: number[];
212
+ rowHeightsPt: number[];
213
+ rows: TableCell[][];
214
+ /** tblPr firstRow flag — first row is a styled header. */
215
+ firstRow: boolean;
216
+ /** tblPr bandRow flag — alternating row banding. */
217
+ bandRow: boolean;
218
+ };
219
+ type GraphicFrameEl = {
220
+ kind: 'graphicFrame';
221
+ transform: Transform;
222
+ table: Table | null;
223
+ /** Extracted chart data (categories/series) when this frame holds a chart. */
224
+ chart: ChartData | null;
225
+ /** Cached fallback raster (charts/SmartArt), if any. */
226
+ fallbackImage: string | null;
227
+ meta: ShapeMeta;
228
+ };
229
+ /** Chart data extracted from ppt/charts/chartN.xml (structure, not visuals). */
230
+ type ChartSeries = {
231
+ name?: string;
232
+ values: number[];
233
+ };
234
+ type ChartData = {
235
+ kind: string;
236
+ title?: string;
237
+ categories: string[];
238
+ series: ChartSeries[];
239
+ };
240
+ type Slide = {
241
+ /** 0-based slide index. */
242
+ index: number;
243
+ /** OPC part path (e.g. "ppt/slides/slide1.xml") — the PptxAnchor.slidePart. */
244
+ part?: string;
245
+ /** cSld name attribute, if any. */
246
+ name?: string;
247
+ /** Layout name (cSld/@name of the slide layout). */
248
+ layoutName?: string;
249
+ /** Derived from the title placeholder text, if present. */
250
+ title?: string;
251
+ /** Speaker notes plain text, if present. */
252
+ notes?: string;
253
+ background: Fill;
254
+ shapes: ShapeNode[];
255
+ };
256
+ /** Document-level metadata from docProps/core.xml + app.xml. */
257
+ type DocMeta = {
258
+ title?: string;
259
+ author?: string;
260
+ lastModifiedBy?: string;
261
+ created?: string;
262
+ modified?: string;
263
+ company?: string;
264
+ app?: string;
265
+ slideCount: number;
266
+ slideWidthPt: number;
267
+ slideHeightPt: number;
268
+ };
269
+ type ParsedPresentation = {
270
+ widthEmu: number;
271
+ heightEmu: number;
272
+ widthPt: number;
273
+ heightPt: number;
274
+ slides: Slide[];
275
+ meta: DocMeta;
276
+ };
277
+ declare const BLACK: Rgba;
278
+ declare const WHITE: Rgba;
279
+ declare const rgbaCss: (c: Rgba) => string;
280
+ declare const rgbaToHex: (c: Rgba) => string;
281
+
282
+ /** Parse options. `skipImageData` avoids embedding image bytes (structure-only runs). */
283
+ type ParseOptions = {
284
+ skipImageData?: boolean;
285
+ };
286
+ declare class PptxParser {
287
+ private pkg;
288
+ private opts;
289
+ private constructor();
290
+ static parse(data: ArrayBuffer | Uint8Array, opts?: ParseOptions): Promise<ParsedPresentation>;
291
+ private run;
292
+ /** Document metadata from docProps/core.xml + app.xml. */
293
+ private parseMeta;
294
+ /** Resolve the layout/master/theme parts for a slide. */
295
+ private resolveChain;
296
+ private loadSlide;
297
+ /** Title = text of the first title/ctrTitle placeholder shape, if any. */
298
+ private deriveTitle;
299
+ /** Speaker notes plain text from the linked notesSlide part, if present. */
300
+ private loadNotes;
301
+ private getSpTree;
302
+ private indexPlaceholders;
303
+ /** Parse a slide/layout/master's spTree. When `bgOnly`, skip placeholders. */
304
+ private parseTree;
305
+ /** Read semantic metadata (name/alt-text/placeholder) from an sp/pic/graphicFrame. */
306
+ private buildMeta;
307
+ private parseShape;
308
+ /** Parse an outer drop shadow from spPr/effectLst/outerShdw. */
309
+ private parseShadow;
310
+ private parsePicture;
311
+ private parseGroup;
312
+ private parseGraphicFrame;
313
+ private parseTable;
314
+ private parseTransform;
315
+ private parseGeometry;
316
+ private firstFillEl;
317
+ private parseFill;
318
+ /** Synchronous fill parse (everything except blip images). */
319
+ private parseFillSync;
320
+ private parseLine;
321
+ private parseBackground;
322
+ private parseTextBody;
323
+ private parseParagraph;
324
+ private parseRun;
325
+ private parseTxStyles;
326
+ private parseOneTxStyle;
327
+ }
328
+ /** Flatten a parsed text body to plain text (paragraphs joined by newlines). */
329
+ declare const textBodyToString: (tb: TextBody) => string;
330
+
331
+ /**
332
+ * Text layout for a DrawingML text body.
333
+ *
334
+ * Produces positioned text segments (top-left origin, PDF points) by wrapping
335
+ * runs within the shape's content box. Because the same segments feed both the
336
+ * (fallback) raster pass and the real-text PDF overlay, the on-screen and
337
+ * selectable text stay perfectly consistent — there is a single source of truth
338
+ * for where each line sits.
339
+ *
340
+ * Phase-1 scope: left/center/right alignment, top/center/bottom vertical anchor,
341
+ * body insets, multi-run lines, bullets (char), line spacing, space before/after.
342
+ * Justify is approximated as left; numbered bullets render as a bullet dot.
343
+ */
344
+
345
+ type TextSegment = {
346
+ text: string;
347
+ xPt: number;
348
+ /** Baseline Y from the top of the slide, in points. */
349
+ baselineYPt: number;
350
+ widthPt: number;
351
+ fontSizePt: number;
352
+ bold: boolean;
353
+ italic: boolean;
354
+ underline: boolean;
355
+ color: Rgba;
356
+ typeface: string;
357
+ };
358
+
359
+ /**
360
+ * SlideRenderer: a resolved `Slide` → a high-DPI background raster (graphics
361
+ * only, **no glyphs**) plus the positioned text segments for the PDF overlay.
362
+ *
363
+ * Everything is drawn in PDF points (the context is pre-scaled by `scale`), so
364
+ * geometry from the parser is used directly. Text is never painted into the
365
+ * raster on the normal path — pdfBuilder draws it as crisp, selectable vectors.
366
+ * The one exception is rotated text shapes, whose glyphs are baked into the
367
+ * raster (so they rotate correctly) while the overlay text is emitted invisible
368
+ * for selection — mirroring the `textBaked` fallback in pptxExport.
369
+ */
370
+
371
+ type RenderedSegment = TextSegment & {
372
+ invisible: boolean;
373
+ rotateDeg: number;
374
+ };
375
+ type RenderedSlide = {
376
+ widthPt: number;
377
+ heightPt: number;
378
+ backgroundPng: string;
379
+ segments: RenderedSegment[];
380
+ };
381
+ declare function renderSlide(slide: Slide, widthPt: number, heightPt: number, scale: number): Promise<RenderedSlide>;
382
+
383
+ /**
384
+ * Structured / semantic projection of a parsed presentation.
385
+ *
386
+ * This is the *business asset*: a clean, render-free representation designed for
387
+ * AI / RAG consumption. `toStructure` maps the render model
388
+ * (`ParsedPresentation`) to a semantic tree (roles, text, bbox, hyperlinks,
389
+ * notes, tables, chart data); `toMarkdown` projects that to LLM-friendly text;
390
+ * `toChunks` produces embedding-ready segments with metadata.
391
+ *
392
+ * The schema is deliberately format-neutral so PDF/DOCX can normalize into the
393
+ * same shape later (the unified DocumentModel).
394
+ */
395
+
396
+ type BBox$1 = {
397
+ xPt: number;
398
+ yPt: number;
399
+ wPt: number;
400
+ hPt: number;
401
+ };
402
+ type StructuredRun = {
403
+ text: string;
404
+ bold?: boolean;
405
+ italic?: boolean;
406
+ underline?: boolean;
407
+ colorHex?: string;
408
+ hyperlink?: string;
409
+ };
410
+ type StructuredParagraph = {
411
+ level: number;
412
+ bullet: 'none' | 'bullet' | 'number';
413
+ runs: StructuredRun[];
414
+ text: string;
415
+ };
416
+ type TextRole = 'title' | 'subtitle' | 'body' | 'other';
417
+ type StructuredElement = {
418
+ type: 'text';
419
+ role: TextRole;
420
+ name?: string;
421
+ bbox: BBox$1;
422
+ paragraphs: StructuredParagraph[];
423
+ text: string;
424
+ } | {
425
+ type: 'image';
426
+ name?: string;
427
+ altText?: string;
428
+ bbox: BBox$1;
429
+ assetId?: string;
430
+ } | {
431
+ type: 'table';
432
+ name?: string;
433
+ bbox: BBox$1;
434
+ headerRow: boolean;
435
+ rows: string[][];
436
+ } | {
437
+ type: 'chart';
438
+ name?: string;
439
+ bbox: BBox$1;
440
+ chart: ChartData;
441
+ summary: string;
442
+ } | {
443
+ type: 'shape';
444
+ name?: string;
445
+ bbox: BBox$1;
446
+ text?: string;
447
+ } | {
448
+ type: 'group';
449
+ bbox: BBox$1;
450
+ children: StructuredElement[];
451
+ };
452
+ type StructuredSlide = {
453
+ index: number;
454
+ name?: string;
455
+ layout?: string;
456
+ title?: string;
457
+ notes?: string;
458
+ elements: StructuredElement[];
459
+ };
460
+ type PresentationStructure = {
461
+ meta: DocMeta;
462
+ slides: StructuredSlide[];
463
+ };
464
+ type Chunk = {
465
+ id: string;
466
+ text: string;
467
+ meta: {
468
+ slide: number;
469
+ role?: string;
470
+ bbox?: BBox$1;
471
+ };
472
+ };
473
+ declare const toStructure: (pres: ParsedPresentation) => PresentationStructure;
474
+ declare const toMarkdown: (structure: PresentationStructure) => string;
475
+ declare const toChunks: (structure: PresentationStructure) => Chunk[];
476
+
477
+ /**
478
+ * Canonical, format-neutral document model.
479
+ *
480
+ * Both PPTX (via the existing structure projector) and PDF normalize into this
481
+ * shape so one set of projectors (Markdown / chunks) and the knowledge base feed
482
+ * off a single representation. Designed to also accept DOCX later.
483
+ *
484
+ * Geometry is optional `BBox` in PDF points, top-left origin. Image/chart
485
+ * elements carry KB-enrichment fields (`ocrText`, `caption`, `summary`) that the
486
+ * vision pipeline fills in (Track 5).
487
+ */
488
+
489
+ type BBox = {
490
+ xPt: number;
491
+ yPt: number;
492
+ wPt: number;
493
+ hPt: number;
494
+ };
495
+
496
+ /**
497
+ * Edit anchors — serializable locators that tie a structural element back to a
498
+ * concrete editable location in the native file.
499
+ *
500
+ * Anchors deliberately carry no live object references and no byte offsets:
501
+ * they cross the API boundary as plain JSON, and the writer/dispatcher
502
+ * re-derives the concrete operators (PDF content-stream indices, OOXML nodes)
503
+ * fresh at apply time by re-rendering / re-parsing. This mirrors how PDF edits
504
+ * already work (operator indices captured at render time) and how the PPTX
505
+ * writer will re-open the raw XML rather than trust the lossy parsed tree.
506
+ */
507
+
508
+ /** Locator into a .pptx part. Built in Track C; consumed by the writer in Track D. */
509
+ type PptxAnchor = {
510
+ fmt: 'pptx';
511
+ /** e.g. "ppt/slides/slide3.xml" */
512
+ slidePart: string;
513
+ /** cNvPr/@id — stable within a slide part. */
514
+ shapeId?: string;
515
+ /** Fallback locator: 0-based shape index in p:spTree. */
516
+ spTreeIndex?: number;
517
+ /** Text edits: paragraph index within the shape's txBody. */
518
+ paraIndex?: number;
519
+ /** ...then run index within that paragraph. */
520
+ runIndex?: number;
521
+ /** Table cell. */
522
+ cell?: {
523
+ row: number;
524
+ col: number;
525
+ };
526
+ /** Image: the blip r:embed relationship id. */
527
+ relId?: string;
528
+ /** Image: resolved media part path (e.g. "ppt/media/image2.png"). */
529
+ mediaPart?: string;
530
+ };
531
+ /**
532
+ * Locator into a PDF. The dispatcher re-renders the page at apply time and
533
+ * relocates the run by text (operator indices are never carried across the
534
+ * boundary, so they can't go stale).
535
+ */
536
+ type PdfAnchor = {
537
+ fmt: 'pdf';
538
+ pageIndex: number;
539
+ /** Element/run text used to relocate the target run at apply time. */
540
+ text?: string;
541
+ /** Geometry in PDF points, top-left origin (informational / planner hints). */
542
+ bbox?: BBox;
543
+ };
544
+ /**
545
+ * Locator into a .xlsx workbook. Rows/cols are 0-based; the writer resolves the
546
+ * A1-style cell reference (`<c r="B3">`) inside the worksheet part at apply time.
547
+ */
548
+ type XlsxAnchor = {
549
+ fmt: 'xlsx';
550
+ /** e.g. "xl/worksheets/sheet1.xml" */
551
+ sheetPart: string;
552
+ /** Display name of the sheet (informational / planner hint). */
553
+ sheetName?: string;
554
+ /** 0-based row index. */
555
+ row: number;
556
+ /** 0-based column index. */
557
+ col: number;
558
+ /** Optional 0-based inclusive range for region-level intents. */
559
+ range?: {
560
+ r1: number;
561
+ c1: number;
562
+ r2: number;
563
+ c2: number;
564
+ };
565
+ };
566
+ type EditAnchor = PptxAnchor | PdfAnchor | XlsxAnchor;
567
+
568
+ /**
569
+ * Format-neutral edit intents — the stable middle of the editing pipeline.
570
+ *
571
+ * Everything above the dispatcher (planner, API, bulk runner, UI) speaks only
572
+ * `EditIntent` / `EditPlan`. `dispatch.ts` is the only place that knows how to
573
+ * route an intent to a concrete engine (PDFEditSession for PDF, PptxWriter for
574
+ * PPTX). Intents are JSON-serializable so they cross the API boundary; the
575
+ * `find-replace` intent is anchor-free (located per file at apply time).
576
+ */
577
+
578
+ type EditIntent = {
579
+ op: 'replace-text';
580
+ anchor: EditAnchor;
581
+ newText: string;
582
+ } | {
583
+ op: 'replace-image';
584
+ anchor: EditAnchor;
585
+ bytes: Uint8Array;
586
+ format: 'png' | 'jpeg';
587
+ mode?: 'swap-part' | 'repoint';
588
+ } | {
589
+ op: 'set-cell';
590
+ anchor: EditAnchor;
591
+ row: number;
592
+ col: number;
593
+ newText: string;
594
+ } | {
595
+ op: 'find-replace';
596
+ find: string;
597
+ replace: string;
598
+ scope?: 'all' | number[];
599
+ } | {
600
+ op: 'move-shape';
601
+ anchor: EditAnchor;
602
+ xPt?: number;
603
+ yPt?: number;
604
+ } | {
605
+ op: 'resize-shape';
606
+ anchor: EditAnchor;
607
+ wPt?: number;
608
+ hPt?: number;
609
+ } | {
610
+ op: 'add-slide';
611
+ afterIndex?: number;
612
+ } | {
613
+ op: 'delete-slide';
614
+ index: number;
615
+ } | {
616
+ op: 'reorder-slide';
617
+ from: number;
618
+ to: number;
619
+ } | {
620
+ op: 'add-text-box';
621
+ anchor: EditAnchor;
622
+ xPt: number;
623
+ yPt: number;
624
+ wPt: number;
625
+ hPt: number;
626
+ text: string;
627
+ sizePt?: number;
628
+ } | {
629
+ op: 'clear-cell';
630
+ anchor: EditAnchor;
631
+ row: number;
632
+ col: number;
633
+ } | {
634
+ op: 'format-cell';
635
+ anchor: EditAnchor;
636
+ row: number;
637
+ col: number;
638
+ format: CellFormatPatch;
639
+ } | {
640
+ op: 'set-column-width';
641
+ anchor: EditAnchor;
642
+ col: number;
643
+ widthChars: number;
644
+ };
645
+ /**
646
+ * Partial formatting for `format-cell` — unset fields inherit the cell's
647
+ * current style. The writer never mutates a shared style entry; it appends.
648
+ */
649
+ type CellFormatPatch = {
650
+ bold?: boolean;
651
+ italic?: boolean;
652
+ /** Number-format code, e.g. '0.00', '#,##0', 'm/d/yyyy'. */
653
+ numFmt?: string;
654
+ /** Solid fill as CSS hex '#RRGGBB'. */
655
+ fill?: string;
656
+ };
657
+ type EditPlan = {
658
+ source: {
659
+ fileName: string;
660
+ format: 'pptx' | 'pdf' | 'xlsx';
661
+ };
662
+ intents: EditIntent[];
663
+ };
664
+ /** Per-intent outcome reported back from the dispatcher. */
665
+ type IntentResult = {
666
+ op: EditIntent['op'];
667
+ ok: boolean;
668
+ applied: number;
669
+ error?: string;
670
+ /** Op-specific payload, e.g. the new slide part path from `add-slide` (for anchoring follow-up intents). */
671
+ detail?: string;
672
+ };
673
+ type EditResult = {
674
+ bytes: Uint8Array;
675
+ results: IntentResult[];
676
+ };
677
+
678
+ type NewRelationship = {
679
+ /** Explicit id (e.g. "rId7"); omitted = next free rIdN is assigned. */
680
+ id?: string;
681
+ type: string;
682
+ /** Target as it should appear in the rels file (usually part-relative, e.g. "../media/image2.png"). */
683
+ target: string;
684
+ external?: boolean;
685
+ };
686
+ declare class PkgWriter {
687
+ private zip;
688
+ private constructor();
689
+ static load(data: ArrayBuffer | Uint8Array): Promise<PkgWriter>;
690
+ has(path: string): boolean;
691
+ /** All part paths in the package (no directory entries). */
692
+ paths(): string[];
693
+ readText(path: string): Promise<string | null>;
694
+ readBinary(path: string): Promise<Uint8Array | null>;
695
+ setText(path: string, xml: string): void;
696
+ setBinary(path: string, bytes: Uint8Array): void;
697
+ /** Add (or overwrite) a part. Content types are managed separately. */
698
+ addPart(path: string, content: string | Uint8Array): void;
699
+ /** Remove a part and its own rels file, if any. */
700
+ removePart(path: string): void;
701
+ /**
702
+ * Add a relationship to `partPath`'s rels file (created when absent).
703
+ * Returns the relationship id.
704
+ */
705
+ addRel(partPath: string, rel: NewRelationship): Promise<string>;
706
+ /** Remove a relationship by id from `partPath`'s rels file. Returns true when found. */
707
+ removeRel(partPath: string, relId: string): Promise<boolean>;
708
+ /** Ensure a `<Default Extension=.../>` mapping exists (no-op when present). */
709
+ ensureDefaultContentType(extension: string, contentType: string): Promise<void>;
710
+ /** Add or replace an `<Override PartName=.../>` for a part (leading slash added). */
711
+ setContentTypeOverride(partPath: string, contentType: string): Promise<void>;
712
+ /** Remove a part's `<Override>`. Returns true when found. */
713
+ removeContentTypeOverride(partPath: string): Promise<boolean>;
714
+ private contentTypesXml;
715
+ save(): Promise<Uint8Array>;
716
+ }
717
+
718
+ /**
719
+ * Slide-level PPTX operations — the only edits that touch multiple parts:
720
+ * add/delete/reorder slides (slide part + rels + `[Content_Types].xml` +
721
+ * `p:sldIdLst` in `ppt/presentation.xml`) and inserting new text boxes.
722
+ *
723
+ * Existing parts are mutated by positional splices only; brand-new parts
724
+ * (slides we create) are fully ours, so they're written whole. Namespace
725
+ * prefixes for splices into *existing* XML are read from the document's own
726
+ * declarations — never assumed.
727
+ */
728
+
729
+ /** Slide parts in presentation order (via `p:sldIdLst` + the presentation rels). */
730
+ declare function slidePartsInOrder(writer: PkgWriter): Promise<string[]>;
731
+
732
+ /**
733
+ * Apply an edit plan to a `.pptx`. Returns the edited bytes (the unchanged
734
+ * input when nothing applied) plus a per-intent result log.
735
+ */
736
+ declare function applyPptxPlan(bytes: Uint8Array, plan: EditPlan): Promise<EditResult>;
737
+
738
+ /**
739
+ * PptxEditSession — the framework-free editing session behind the slide editor
740
+ * UI (and any other host: React today, mobile shells later).
741
+ *
742
+ * Intent-sourced: the UI never mutates a live model. It calls `apply(intents)`;
743
+ * the writer splices the current bytes (`applyPptxPlan`), the session snapshots
744
+ * the result, re-parses, and notifies subscribers, which re-render from the
745
+ * *saved* truth — WYSIWYG can't drift from what's actually in the file. Undo /
746
+ * redo replay byte snapshots (bounded), so they're trivially correct.
747
+ */
748
+
749
+ type SessionListener = () => void;
750
+ declare class PptxEditSession {
751
+ readonly fileName: string;
752
+ /** Byte snapshots; `cursor` points at the current state, entries after it are redo states. */
753
+ private snapshots;
754
+ private cursor;
755
+ private parsed;
756
+ private listeners;
757
+ /** Every intent ever applied, in order (serializable audit / AI parity log). */
758
+ readonly intentLog: EditIntent[];
759
+ private constructor();
760
+ static load(fileName: string, bytes: Uint8Array): Promise<PptxEditSession>;
761
+ /** Current file bytes (the saved truth). */
762
+ get bytes(): Uint8Array;
763
+ /** Current parsed render model. */
764
+ get presentation(): ParsedPresentation;
765
+ get canUndo(): boolean;
766
+ get canRedo(): boolean;
767
+ subscribe(listener: SessionListener): () => void;
768
+ private notify;
769
+ /**
770
+ * Apply intents to the current bytes. When anything lands, a new snapshot
771
+ * becomes current, redo is cleared, and the model is re-parsed. Failed
772
+ * intents are reported per-intent, never thrown.
773
+ */
774
+ apply(intents: EditIntent[]): Promise<IntentResult[]>;
775
+ undo(): Promise<boolean>;
776
+ redo(): Promise<boolean>;
777
+ /** Current bytes as a downloadable Blob. */
778
+ toBlob(): Blob;
779
+ }
780
+
781
+ /**
782
+ * Zod schemas for the edit contract — the validation boundary between an
783
+ * (untrusted) LLM-emitted `EditPlan` and the dispatcher (PLATFORM_PLAN.md
784
+ * Phase 5). Two consumers:
785
+ *
786
+ * 1. **Runtime validation** — `validateEditPlan(json)` at `/api/edit/apply`
787
+ * (and any agent host) rejects malformed plans with readable errors before
788
+ * a single intent runs.
789
+ * 2. **Tool definitions** — `editPlanJsonSchema()` emits a JSON Schema an agent
790
+ * framework can hand an LLM verbatim as the arguments schema for an
791
+ * "apply document edits" tool. The model then emits plans that validate.
792
+ *
793
+ * The schemas mirror the `EditIntent` / `EditAnchor` unions in this directory
794
+ * exactly; keep them in lockstep when the unions change (the round-trip test
795
+ * asserts every op is covered). `replace-image` carries binary bytes, so it is
796
+ * `z.any()` here — image replacement uses a binary channel, not pure JSON.
797
+ */
798
+
799
+ type ValidatedPlan = {
800
+ ok: true;
801
+ plan: EditPlan;
802
+ } | {
803
+ ok: false;
804
+ errors: string[];
805
+ };
806
+ /**
807
+ * Validate an untrusted value as an `EditPlan`. On success returns the typed
808
+ * plan; on failure a flat list of `path: message` strings (safe to surface to
809
+ * the caller / feed back to the model).
810
+ */
811
+ declare function validateEditPlan(input: unknown): ValidatedPlan;
812
+ /**
813
+ * JSON Schema for the whole `EditPlan` — hand this to an agent framework as the
814
+ * arguments schema of an "apply document edits" tool. Binary/unrepresentable
815
+ * fields (`replace-image.bytes`) render as permissive `{}`.
816
+ */
817
+ declare function editPlanJsonSchema(): Record<string, unknown>;
818
+
819
+ type ProgressFn = (done: number, total: number) => void;
820
+ declare function pptxToPdf(blob: Blob, onProgress?: ProgressFn): Promise<Blob>;
821
+
822
+ /** Shared license/activation contracts used by both the engine gate and the server. */
823
+ /** Capabilities a license can unlock. `view`/`annotate` are the always-available base. */
824
+ type LicenseFeature = 'view' | 'annotate' | 'edit-text' | 'sign' | 'redact' | 'forms' | 'export-clean' | 'pptx-edit' | 'sheets-view' | 'sheets-edit';
825
+ type LicenseEdition = 'trial' | 'pro' | 'enterprise';
826
+ type LicenseStatus = 'UNLICENSED' | 'INVALID' | 'LICENSED' | 'GRACE' | 'DEGRADED';
827
+ interface LicenseState {
828
+ status: LicenseStatus;
829
+ edition: LicenseEdition | null;
830
+ features: LicenseFeature[];
831
+ /** True when render/export should be watermarked. */
832
+ watermark: boolean;
833
+ /** Human-readable reason (for diagnostics; never user-facing copy). */
834
+ reason: string;
835
+ }
836
+
837
+ /**
838
+ * Client-side license gate for the NamahaPDF engine.
839
+ *
840
+ * Flow (see configureLicense):
841
+ * 1. Verify the license key OFFLINE (signature, expiry, domain) — instant, no network.
842
+ * 2. If a cached activation token is still valid → LICENSED immediately.
843
+ * 3. Kick a NON-BLOCKING background activation that exchanges the key for a fresh,
844
+ * short-TTL activation token (which is also what the server can revoke).
845
+ *
846
+ * Offline grace is weighted heavily on purpose: transient network/server failures
847
+ * never punish a paying customer. We tolerate up to MAX_FAILURES (5) attempts AND a
848
+ * BOOTSTRAP_GRACE window before degrading — the SDK degrades immediately ONLY on an
849
+ * explicit signed `revoked`/`invalid` from the server.
850
+ *
851
+ * Everything is injectable (storage / fetch / clock / public key) so it is unit-testable
852
+ * without a browser or a server.
853
+ */
854
+
855
+ interface StorageLike {
856
+ getItem(key: string): string | null;
857
+ setItem(key: string, value: string): void;
858
+ }
859
+ interface LicenseConfig {
860
+ /** The signed license key the developer received. */
861
+ licenseKey?: string;
862
+ /** Activation endpoint. Defaults to a same-origin `/api/license/activate`. */
863
+ activationUrl?: string;
864
+ /** Stable per-install id; auto-generated + persisted when omitted. */
865
+ deviceId?: string;
866
+ /** First-party bypass — our own app passes this so it never watermarks itself. */
867
+ owner?: boolean;
868
+ /** Override the embedded public key (tests only). */
869
+ publicKeyHex?: string;
870
+ /** Injected clock (tests). */
871
+ now?: () => number;
872
+ /** Injected storage (tests / Node). */
873
+ storage?: StorageLike;
874
+ /** Injected fetch (tests / Node). */
875
+ fetchImpl?: typeof fetch;
876
+ /** Host to domain-check against; defaults to location.hostname in the browser. */
877
+ host?: string;
878
+ /** Background retry backoff in ms (tests can shorten). */
879
+ retryDelays?: number[];
880
+ }
881
+ type Listener = (s: LicenseState) => void;
882
+ /**
883
+ * Configure (or reconfigure) the license gate. Verifies the key synchronously and
884
+ * starts a background activation. Returns the immediate state; subscribe with
885
+ * `onLicenseChange` for updates as activation completes.
886
+ */
887
+ declare function configureLicense(config?: LicenseConfig): LicenseState;
888
+ /** Current license state (synchronous, cheap). */
889
+ declare function getLicenseState(): LicenseState;
890
+ declare function isFeatureEnabled(feature: LicenseFeature): boolean;
891
+ /** Throws if a feature is not licensed — use to guard gated engine operations. */
892
+ declare function assertFeature(feature: LicenseFeature): void;
893
+ declare function shouldWatermark(): boolean;
894
+ /** Subscribe to state changes (activation success/failure). Returns an unsubscribe. */
895
+ declare function onLicenseChange(cb: Listener): () => void;
896
+
897
+ export { BLACK, type BulletKind, type CellFormatPatch, type ChartData, type ChartSeries, type Chunk, type DocMeta, type EditAnchor, type EditIntent, type EditPlan, type EditResult, type Fill, type Geometry, type GradientStop, type GraphicFrameEl, type GroupEl, type IntentResult, type LicenseConfig, type LicenseEdition, type LicenseFeature, type LicenseState, type LicenseStatus, type Paragraph, type ParsedPresentation, type PathSeg, type PictureEl, type PptxAnchor, PptxEditSession, PptxParser, type PresentationStructure, type ProgressFn, type RenderedSegment, type RenderedSlide, type Rgba, type Run, type SessionListener, type Shadow, type ShapeEl, type ShapeMeta, type ShapeNode, type Slide, type SrcRect, type Stroke, type StructuredElement, type StructuredParagraph, type StructuredRun, type StructuredSlide, type Table, type TableCell, type TextBody, type TextRole, type Transform, WHITE, applyPptxPlan, assertFeature, configureLicense, editPlanJsonSchema, getLicenseState, isFeatureEnabled, onLicenseChange, pptxToPdf, renderSlide, rgbaCss, rgbaToHex, shouldWatermark, slidePartsInOrder, textBodyToString, toChunks, toMarkdown, toStructure, validateEditPlan };