cli
The same engine from the command line: render, inspect, validate, round-trip and bisect, in Node.
The same engine from the command line: render slides without a browser, inspect, validate, round-trip, bisect.
npm install @pptx-studio/cli
What it is
pptx-studio render draws every slide to SVG with no browser and no LibreOffice, measuring text from the font tables on the machine. inspect, validate, roundtrip and bisect are the other packages' functions with a file argument and an exit code. renderDeck is the same renderer from a program.
This is the one package that imports Node. Everything it knows about .pptx lives in the browser packages beneath it.
30-second example
const { slides, fonts, missing } = renderDeck(bytes, { width: 1280 });
slides[0]?.svg; // stands alone: pictures as data: URIs, defs inline
fonts.filter((face) => face.substituted); // [{ asked: 'Aptos', drawn: 'Carlito', file, substituted: true }]
missing; // code points no indexed face could drawKey concepts
Everything it knows lives somewhere else
The CLI adds files, arguments and exit codes to the browser packages. A question answered on the command line and one answered in a tab cannot drift, because they are the same function.
render measures with font tables
With no canvas, the CLI indexes the machine's font files, reads advances and kerning out of the SFNT tables, and measures from those. Which face drew which run is reported, never hidden.
A machine with no fonts substitutes everything
A container that ships no faces draws every typeface in a fallback and the report says so - that is where you would find out, rather than in a wrong-looking picture.
No PNG, no shaping
Output is SVG. Complex scripts are measured without a shaper, so Arabic and Devanagari measure wide; the face box follows the machine that renders.
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;
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
Try it live
Reading the deck…