Command line
pptx-studio render, inspect, validate, roundtrip and bisect - the browser packages with a file argument and an exit code.
The same engine from the command line: render slides without a browser, inspect, validate, round-trip, bisect.
npm install @pptx-studio/cli
Run it
npx @pptx-studio/cli <verb> <file> [options]Node 24 or newer. The CLI is the one package that imports Node; everything it knows about .pptx
lives in the browser packages beneath it, so an answer on the command line and the same answer in
a tab cannot drift.
The verbs
| Verb | What it does | Exit 1 when |
|---|---|---|
render | every slide, or one, as a standalone SVG; text measured from font tables | no face could be found at all, or a bad width |
inspect | the census, as text or --json | a structural error was found |
validate | the 34 rules; the three baseline rules are skipped and named | anything fatal |
roundtrip | read, write back, compare canonically; --write saves the output | any difference, or the export refused |
bisect | ddmin over the changes between two packages, against an oracle | - |
resolve | not built yet; it arrives with phase 7 | - |
render
npx @pptx-studio/cli render deck.pptx --out slides/ --width 1280
npx @pptx-studio/cli render deck.pptx --slide 3 --width 640 --out slide-3.svg --json
npx @pptx-studio/cli render deck.pptx --font-dir ./fonts --no-system-fonts --out slides/
npx @pptx-studio/cli render deck.pptx --no-text --out geometry/| Option | Meaning |
|---|---|
--out <path> | a directory for every slide, or a file for --slide |
--width <px> | the SVG width; the height follows the slide's aspect (default 1920) |
--slide <n> | one slide, one-based |
--font-dir <dir> | a directory of font files to index, in addition to the system's |
--no-system-fonts | do not scan the system font directories |
--no-text | geometry only; no face is indexed |
--json | print the font report: every face asked for, what drew it, what was missing |
From a program, renderDeck(bytes, { slide, width, fontDirs, systemFonts, text }) returns
{ slides, width, height, fonts, missing, fontDirectories, facesIndexed }.
Fonts
There is no canvas in Node, so render indexes the machine's font files and reads advances and
kerning out of their tables. Which face drew each run is reported in fonts, with substituted
set where the face asked for was not there. A container that ships no fonts draws every typeface in
a fallback, and says so - that is where you find out, not in a wrong-looking picture.
The face box - ascent, descent, the line's ideographic baseline - is measured per rasteriser, so the same deck renders a whisker differently on Windows and Linux. There is no shaper: Arabic and Devanagari measure wide. Output is SVG; there is no PNG.
inspect
npx @pptx-studio/cli inspect deck.pptx
npx @pptx-studio/cli inspect deck.pptx --json --parts --namespacesThe census, as @pptx-studio/census computes it in a browser: archive, parts, relationships,
features keyed to the plan's phases, namespaces, problems.
validate
npx @pptx-studio/cli validate deck.pptxTwenty-eight rules run on a file by itself; V027 to V029 need the package as it was opened, so they are listed as skipped with the reason. Each finding names the rule, the part and the XPath.
roundtrip
npx @pptx-studio/cli roundtrip deck.pptx
npx @pptx-studio/cli roundtrip deck.pptx --write out.pptxReads the deck, writes it back through the writer, and compares the two: XML parts canonically, relationship parts as graphs with opaque ids, binary parts by digest. This is the comparison the repository's round-trip badge is made of.
bisect
npx @pptx-studio/cli bisect before.pptx after.pptx --oracle validate
npx @pptx-studio/cli bisect before.pptx after.pptx --oracle powerpointGiven a deck that works and one that does not, finds the smallest set of changes between them that
still reproduces the failure. The validate oracle runs anywhere; the powerpoint oracle drives
PowerPoint through COM with OpenAndRepair off, and needs a Windows machine with Office.
Errors
Throws RenderError, whose code is one of:
- CLI_FONT_UNREADABLE
- CLI_FONT_DIR
- CLI_NO_FACE
- CLI_FONT_SIZE
- CLI_NO_SLIDE
- CLI_WIDTH
- CLI_OUTPUT_PATH
API reference
28 exports of @pptx-studio/cli 0.4.4, read from the package this site installed. Click a name for its declaration.
src/main.ts
Streamstypeinterface Streams { readonly out: (text: string) => void; readonly err: (text: string) => void; }mainfunctionParse, dispatch, and return an exit code. Never callsprocess.exit.function main(argv: readonly string[], streams?: Streams): number;
src/inspect.ts
InspectOptionstypepptx-studio inspect deck.pptxinterface InspectOptions { readonly json: boolean; readonly parts: boolean; readonly namespaces: boolean; readonly top: number; /** Write to this path instead of stdout. */ readonly out: string | null; }INSPECT_DEFAULTSconstconst INSPECT_DEFAULTS: InspectOptions;
InspectResulttypeinterface InspectResult { readonly census: PackageCensus; readonly output: string; /** * Process exit code. * * `1` when the census found something it classes as an error, `0` otherwise - * a warning or a note never fails the command. That distinction is what makes * `inspect` usable in a script: "did this deck load, and is anything in it * structurally broken" is a yes/no question, while "is anything in it * unusual" is not. */ readonly exitCode: number; }inspectFilefunctionRun a census over one file and render it. Does no I/O of its own beyond the read.function inspectFile(path: string, options?: InspectOptions): InspectResult;
runInspectfunctionRuninspectFileand put the result where the options say.function runInspect(path: string, options: InspectOptions, write: (text: string) => void): number;
src/render/sfnt.ts
FontBackendtypeThe rasteriser a Chromium reads a face's vertical metrics through.type FontBackend = 'directwrite' | 'freetype' | 'coretext';
backendForfunctionFreeType on Linux, CoreText on macOS, DirectWrite elsewhere. ADR 0045, ADR 0053.function backendFor(platform: string): FontBackend;
Facetypeinterface Face { /** `name` ID 1, which is what PowerPoint matches a typeface on. */ readonly family: string; /** `name` ID 2: `Regular`, `Bold`, `Italic`, `Bold Italic`, or a style name. */ readonly subfamily: string; /** `name` ID 16, present when the family splits into more than four styles. */ readonly typographicFamily: string | undefined; readonly metrics: FaceMetrics; readonly bold: boolean; readonly italic: boolean; /** Code point to advance, in font units. */ advanceOf(codePoint: number): number | undefined; /** The adjustment between two code points, in font units. Usually zero. */ kernBetween(left: number, right: number): number; }facesInfunctionEvery face in a file, which is one unless the file is a collection.function facesIn(bytes: Uint8Array, subject: string, backend: FontBackend): readonly Face[];
src/render/faces.ts
systemFontDirectoriesfunctionWhere fonts live on this platform, whether or not the directories exist.function systemFontDirectories(platform?: string): readonly string[];
FontLibrarytypeinterface FontLibrary { /** Every face found, for the diagnostics and for a test to count. */ readonly indexed: readonly IndexedFace[]; /** The directories that were scanned, in order. */ readonly directories: readonly string[]; resolve(family: string, bold: boolean, italic: boolean): Resolved | undefined; }indexFontsfunctionRead every font file under the given directories, once.function indexFonts(options?: IndexOptions): FontLibrary;
src/render/measure.ts
FaceUsetypeinterface FaceUse { readonly asked: string; readonly drawn: string; readonly file: string; readonly substituted: boolean; }FontMeasurertypeinterface FontMeasurer { readonly measurer: TextMeasurer; readonly faceBox: FaceBoxProbe; /** The CSS family list a run is drawn in: the face that measured it, then the deck's own stack. */ readonly cssFamilyFor: (font: RunFont) => string; /** Every typeface asked for, and what it was drawn in. */ used(): readonly FaceUse[]; /** Code points no face in the library could draw. */ missing(): readonly number[]; }createFontMeasurerfunctionA measurer bound to one library.function createFontMeasurer(library: FontLibrary): FontMeasurer;
src/render/render.ts
DEFAULT_WIDTHconstWhat a slide is drawn at when the caller says nothing. PowerPoint's own.const DEFAULT_WIDTH = 1920;
RenderDeckOptionstypeWhat to draw. Every field has a default, sorenderDeck(bytes)is a whole call.interface RenderDeckOptions { /** 1-based. Omitted, or `null`, is every slide. */ readonly slide?: number | null; /** The `width` attribute in CSS pixels; the height follows the deck's aspect. */ readonly width?: number; /** Searched before the platform's own, so a face can be overridden without installing it. */ readonly fontDirs?: readonly string[]; /** Also look in this platform's font directories. Default true. */ readonly systemFonts?: boolean; /** Draw text. `false` draws geometry only and asks no font questions. Default true. */ readonly text?: boolean; }RenderOptionstypeWhat the verb adds: where the markup goes, and what is said about it.interface RenderOptions extends RenderDeckOptions { /** A directory for many slides, a file for one, or `null` for stdout. */ readonly out: string | null; readonly json: boolean; readonly quiet: boolean; }RenderedSlidetypeinterface RenderedSlide { /** 1-based, as the flag and the file name spell it. */ readonly number: number; readonly svg: string; }RenderResulttypeinterface RenderResult { readonly slides: readonly RenderedSlide[]; readonly width: number; readonly height: number; /** Every typeface the deck asked for, and what drew it. Empty with `--no-text`. */ readonly fonts: readonly FaceUse[]; /** Code points no indexed face could draw. */ readonly missing: readonly number[]; readonly fontDirectories: readonly string[]; readonly facesIndexed: number; }renderDeckfunctionRender a deck that is already in memory.function renderDeck(bytes: Uint8Array, options?: RenderDeckOptions): RenderResult;
runRenderfunctionRead, render, write, and return an exit code. Never callsprocess.exit.function runRender(file: string, options: RenderOptions, out: (text: string) => void): number;
src/render/errors.ts
RenderErrorCodetypeTyped failures for rendering outside a browser.type RenderErrorCode = /** Bytes that are not an SFNT font, or a table the reader needs is absent. */ 'CLI_FONT_UNREADABLE' | /** A font directory that was named on the command line and does not exist. */ 'CLI_FONT_DIR' | /** No face on this machine can stand in for a typeface the deck names. */ 'CLI_NO_FACE' | /** A run whose resolved size is not a positive number of hundredths of a point. */ 'CLI_FONT_SIZE' | /** A slide index outside the deck. */ 'CLI_NO_SLIDE' | /** A width that is not a positive whole number of pixels. */ 'CLI_WIDTH' | /** An output path that names no directory, or a directory that is a file. */ 'CLI_OUTPUT_PATH';
RENDER_ERROR_CODESconstconst RENDER_ERROR_CODES: readonly RenderErrorCode[];
RenderErrorclassclass RenderError extends Error { readonly name = "RenderError"; readonly code: RenderErrorCode; /** The font file, deck path or typeface the failure is about. */ readonly subject: string; constructor(code: RenderErrorCode, message: string, subject: string); }isRenderErrorfunctionfunction isRenderError(error: unknown): error is RenderError;
Try it
Reading the deck…