pptx-kit-preview 0.0.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,103 @@
1
1
  # pptx-kit-preview
2
2
 
3
- Experimental preview renderer for [pptx-kit](https://github.com/baseballyama/pptx-kit): render slides to SVG (browser + Node) or rasterize to PNG in Node, with no browser binary.
3
+ Preview renderer for [`pptx-kit`](https://github.com/baseballyama/pptx-kit).
4
+ Turns a `pptx-kit` slide model into an **SVG** (browser + Node) or rasterizes
5
+ it to a **PNG / RGBA image in Node — with no headless browser**.
4
6
 
5
- This `0.0.0` release is a name reservation. The real package ships shortly from
6
- [baseballyama/pptx-kit](https://github.com/baseballyama/pptx-kit) watch the repo for the first release.
7
+ > **Experimental (0.x).** This package lives in the `pptx-kit` monorepo and
8
+ > also powers the docs-site playground and the fidelity harness. The renderer
9
+ > is an _approximation_ of PowerPoint / LibreOffice output and is still
10
+ > evolving; the API may change between minor versions, and `0.x` semver applies
11
+ > (a minor bump may break). See [fidelity](#fidelity) below.
12
+
13
+ ## Why
14
+
15
+ `pptx-kit` core does not render — by design. But "show me this deck" comes up
16
+ constantly: a docs playground, a thumbnail service, a visual-diff test. The
17
+ hard requirement is that rendering must work **in Node and rasterize to an
18
+ image without spawning a browser**, so it fits CI and serverless. This package
19
+ lays text out as pure SVG `<text>` (no `<foreignObject>`) and paints it with
20
+ [resvg](https://github.com/yisibl/resvg-js), which has no browser dependency.
21
+
22
+ ## Entry points
23
+
24
+ | Import | Runtime | Use |
25
+ | ----------------------- | -------------- | --------------------------------------------------- |
26
+ | `pptx-kit-preview` | browser + Node | `renderSlideToSvg` → an SVG string |
27
+ | `pptx-kit-preview/node` | Node only | `renderSlideToImage` / `renderSlideToRgba` → pixels |
28
+
29
+ The browser entry pulls in **no** Node built-ins (no `node:fs`, resvg, or
30
+ fontkit), so it bundles cleanly for the web.
31
+
32
+ ## Usage
33
+
34
+ ### SVG (browser or Node)
35
+
36
+ ```ts
37
+ import { renderSlideToSvg } from 'pptx-kit-preview';
38
+ import { loadPresentation, getSlides } from 'pptx-kit';
39
+
40
+ const pres = await loadPresentation(bytes);
41
+ const svg = renderSlideToSvg(pres, getSlides(pres)[0]);
42
+ // → '<svg …>…</svg>' (text laid out via <foreignObject> — the browser wraps it)
43
+ ```
44
+
45
+ ### PNG / RGBA (Node, no browser)
46
+
47
+ ```ts
48
+ import { renderSlideToImage, renderSlideToRgba } from 'pptx-kit-preview/node';
49
+ import { loadPresentationFile, getSlides } from 'pptx-kit/node';
50
+
51
+ const pres = await loadPresentationFile('deck.pptx');
52
+ const slide = getSlides(pres)[0];
53
+
54
+ // PNG-encoded bytes:
55
+ const png = renderSlideToImage(pres, slide, { width: 1280 });
56
+
57
+ // Raw RGBA pixels (+ the same frame PNG-encoded), for SSIM / diffing:
58
+ const { image, png: png2 } = renderSlideToRgba(pres, slide, { width: 1280 });
59
+ // image: { width, height, data: Uint8Array } // row-major RGBA
60
+ ```
61
+
62
+ The Node path lays text out as pure `<text>` and measures it with a fontkit
63
+ measurer over **bundled** metric-compatible fonts (Carlito ≈ Calibri, Caladea ≈
64
+ Cambria, Liberation ≈ Arial/Times/Courier; OFL / Apache-2.0, see
65
+ `fonts/LICENSES.md`). The measurer, resvg's font set, and the SVG family names
66
+ all reference the same fonts, so wrap/positioning math agrees with the painted
67
+ glyphs and the result is deterministic (no system fonts).
68
+
69
+ ## Fidelity
70
+
71
+ This is a high-fidelity preview, not a spec-complete PowerPoint renderer.
72
+ Preset and custom geometry, solid/gradient/pattern/image fills (including the
73
+ placeholder layout/master cascade), strokes, rotation, effects (shadow, glow,
74
+ soft edge, reflection), images with adjustments, charts (column, bar, line,
75
+ area, pie, doughnut, scatter, radar, bubble), tables with per-run cell text,
76
+ vertical and multi-column text in both text-layout modes, picture bullets, and
77
+ template (layout/master) decoration all render. SmartArt, animations, 3D, and
78
+ EMF/WMF fall back to labelled placeholders carrying a machine-readable marker
79
+ (below). Per-slide closeness to a LibreOffice baseline is measured and gated
80
+ in CI by the fidelity harness in the monorepo (`site/fidelity`) — mean
81
+ fg-SSIM ≈ 0.78 across the corpus, with the residual gaps documented there.
82
+
83
+ ### Fallback markers
84
+
85
+ When a shape cannot be rendered (unsupported format, missing bytes, or
86
+ unrecognised content type), the renderer emits a labelled placeholder rectangle.
87
+ The placeholder's top-level `<g>` element carries a `data-pptx-fallback`
88
+ attribute so automated tooling can detect partial renders without string-parsing
89
+ the label text:
90
+
91
+ | Value | Trigger |
92
+ | ---------------- | ----------------------------------------------------------- |
93
+ | `"image"` | Image bytes missing (external link) or format not decodable |
94
+ | `"chart"` | Chart kind not modelled by this renderer |
95
+ | `"graphicFrame"` | Graphic frame with no recognised content (SmartArt, etc.) |
96
+ | `"custGeom"` | Shape uses custom geometry (`<a:custGeom>`) |
97
+
98
+ Example: `svg.querySelectorAll('[data-pptx-fallback]')` lists every shape that
99
+ did not fully render.
100
+
101
+ ## License
102
+
103
+ MIT (code). Bundled fonts: OFL-1.1 / Apache-2.0 — see `fonts/LICENSES.md`.
@@ -0,0 +1,42 @@
1
+ import { PresentationData, SlideData } from 'pptx-kit';
2
+
3
+ /** What a measurer needs to size one run. Pixels at 96 DPI; the caller has
4
+ * already applied EMU→px, pt→px and the autofit fontScale. `family` is the
5
+ * resolved internal family name (see `substituteFamily`), not a CSS list. */
6
+ interface FontSpec {
7
+ readonly family: string;
8
+ readonly sizePx: number;
9
+ readonly bold: boolean;
10
+ readonly italic: boolean;
11
+ readonly letterSpacingPx: number;
12
+ }
13
+ /** Advance width of `text` in px, plus optional vertical metrics. A real
14
+ * measurer returns ascent/descent/lineGap so line height matches the font;
15
+ * the heuristic returns width only and the engine falls back to a ratio. */
16
+ interface MeasureResult {
17
+ readonly widthPx: number;
18
+ readonly ascentPx?: number;
19
+ readonly descentPx?: number;
20
+ readonly lineGapPx?: number;
21
+ }
22
+ type TextMeasurer = (text: string, spec: FontSpec) => MeasureResult;
23
+ type TextLayoutMode = 'foreignObject' | 'svg';
24
+ interface RenderSlideOptions {
25
+ /** Measurer used by the pure-SVG text path. Required when `textLayout` is
26
+ * 'svg'; ignored otherwise. */
27
+ readonly measureText?: TextMeasurer;
28
+ /** Which text path to use. Defaults to 'foreignObject' (the browser path)
29
+ * so existing callers are unaffected; the harness opts into 'svg'. */
30
+ readonly textLayout?: TextLayoutMode;
31
+ }
32
+ declare const SANS = "Carlito";
33
+ declare const SERIF = "Caladea";
34
+ declare const ARIAL = "Liberation Sans";
35
+ declare const TIMES = "Liberation Serif";
36
+ declare const MONO = "Liberation Mono";
37
+ declare const substituteFamily: (family: string | null | undefined) => string;
38
+ declare const defaultMeasurer: TextMeasurer;
39
+
40
+ declare const renderSlideSvg: (pres: PresentationData, slide: SlideData, opts?: RenderSlideOptions) => string;
41
+
42
+ export { ARIAL, type FontSpec, MONO, type MeasureResult, type RenderSlideOptions, SANS, SERIF, TIMES, type TextLayoutMode, type TextMeasurer, defaultMeasurer, renderSlideSvg as renderSlideToSvg, substituteFamily };