PPTX Studio

Command line

pptx-studio render, inspect, validate, roundtrip and bisect - the browser packages with a file argument and an exit code.

@pptx-studio/cli0.4.4runs inNodeLive demo →npm →

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

VerbWhat it doesExit 1 when
renderevery slide, or one, as a standalone SVG; text measured from font tablesno face could be found at all, or a bad width
inspectthe census, as text or --jsona structural error was found
validatethe 34 rules; the three baseline rules are skipped and namedanything fatal
roundtripread, write back, compare canonically; --write saves the outputany difference, or the export refused
bisectddmin over the changes between two packages, against an oracle-
resolvenot 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/
OptionMeaning
--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-fontsdo not scan the system font directories
--no-textgeometry only; no face is indexed
--jsonprint 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 --namespaces

The 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.pptx

Twenty-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.pptx

Reads 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 powerpoint

Given 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

  • Streamstype
    interface Streams {
      readonly out: (text: string) => void;
      readonly err: (text: string) => void;
    }
  • mainfunctionParse, dispatch, and return an exit code. Never calls process.exit.
    function main(argv: readonly string[], streams?: Streams): number;

src/inspect.ts

  • InspectOptionstypepptx-studio inspect deck.pptx
    interface 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_DEFAULTSconst
    const INSPECT_DEFAULTS: InspectOptions;
  • InspectResulttype
    interface 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;
  • runInspectfunctionRun inspectFile and 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;
  • Facetype
    interface 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[];
  • FontLibrarytype
    interface 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

  • FaceUsetype
    interface FaceUse {
      readonly asked: string;
      readonly drawn: string;
      readonly file: string;
      readonly substituted: boolean;
    }
  • FontMeasurertype
    interface 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, so renderDeck(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;
    }
  • RenderedSlidetype
    interface RenderedSlide {
      /** 1-based, as the flag and the file name spell it. */
      readonly number: number;
      readonly svg: string;
    }
  • RenderResulttype
    interface 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 calls process.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_CODESconst
    const RENDER_ERROR_CODES: readonly RenderErrorCode[];
  • RenderErrorclass
    class 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);
    }
  • isRenderErrorfunction
    function isRenderError(error: unknown): error is RenderError;

Try it

Reading the deck…

Open the full demo →

On this page