Skip to content
CompendiumLabsPublic

About

Gum graphics CLI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Gum

Layered red-to-blue wave packets

Make plots, diagrams, math, and slides with JSX.
Render them right from your terminal.

Live Demo · Documentation · Gallery

Install

Gum is a JSX language for vector graphics. The CLI is the fastest way in: write a figure in a .jsx file, render it with gum, and keep the source alongside your project. Output formats include SVG, PNG, PDF, PPTX, MP4, and kitty graphics. Elements, math functions, colors, and layout helpers are already in scope.

The bundled npm CLI in the gum-jsx package requires Node.js 24+. One can either run it with npx gum-jsx or install it globally with:

npm install -g gum-jsx@latest
gum --version

This installs the gum command for JSX rendering. The npm package contains a prebuilt JavaScript bundle, fonts, map data, and the PNG and MP4 renderers. It has no runtime package dependencies. Bun 1.4 or newer works equally well as an alternative runtime.

For agents: start with gum docs, then search for the components and examples you need. The installed command includes its own version-matched documentation; no separate skill or network connection is required.

Built-in documentation

gum docs                              # Orientation, workflow, and starting points
gum docs search "axis labels"          # Ranked page IDs and descriptions
gum docs get elements/Plot             # One reference with its JSX example
gum docs get guides/units
gum docs list guides                   # Browse guides, elements, or gallery
gum docs example gallery/pendulum_physics > pendulum.jsx
gum render pendulum.jsx -o pendulum.png

gum docs example writes only JSX to stdout. For an example with data files, export the source and fixtures together:

gum docs example guides/load_csv --output example
gum render example/load_csv.jsx -o example.svg

The output directory is created if needed; existing example files are replaced. Internal reference links use page IDs that can be passed to gum docs get. Use gum docs --help for commands and gum render --help for rendering options.

Quickstart

Save this as test.jsx:

<Box font-size={px(18)} padding={em(1.5)} border-width={px(1)} border-color={gray}>
  <VStack gap={em(1)}>
    <Text font-size={em(1.6)} font-weight={bold}>Welcome to Gum!</Text>
    <Text line-height={em(1.45)}>This is a basic example demonstrating a horizontal stack.</Text>
    <HStack height={em(8)} gap={em(1)}>
      <RoundedRect aspect={1} border-radius={em(1)} fill={blue} stroke={none} />
      <Circle fill={red} stroke={none} />
      <RoundedRect aspect={1} border-radius={em(1)} fill={green} stroke={none} />
    </HStack>
  </VStack>
</Box>

Then run some of the following commands to render it:

gum test.jsx -o test.svg
gum test.jsx -o test.png
gum test.jsx -o test.pdf
gum test.jsx # Display inline in a kitty-compatible terminal
Test rendered from test.jsx

Change the function, limits, or colors and render it again. Use px(24) for pixels, em(1.5) for font-relative lengths, and fractions such as 0.5 for relative sizes. Head to gum studio for a live browser-based editor.

Usage

gum reads from stdin if you omit the input or pass -. Input and output paths are relative to the directory where you run the command. An output extension selects SVG, PNG, MP4, PDF, or PPTX. For a file or stdin, gum defaults to kitty graphics on stdout; directories and multiple files default to PDF. Use -f svg to send SVG text to stdout. The full options are below.

Run gum render [options] [files...], or use the shorthand gum [options] [files...]. Only a first argument of render or docs selects a command; otherwise Gum uses the existing rendering syntax. Both rendering forms accept stdin when no file is supplied.

Option Meaning
files... JSX files or one deck directory; omit or use - for stdin.
-f, --format <format> Output format: kitty, svg, png, pdf, pptx, mp4, tree, or json. Defaults to kitty or the output extension for a file; directories and multiple files require PDF or PPTX.
-o, --output <file> Write to a file instead of stdout.
--time <seconds> Preview one video frame; defaults to 0. Omit for MP4 export.
--qp <number> MP4 quantizer, 10–51; lower is higher quality. Default: 18.
-W, --width <pixels> Set the viewport width.
-H, --height <pixels> Set the viewport height.
-r, --ratio <number> PNG/kitty sampling ratio, default 1.
--select <x,y,width,height> Crop PNG/kitty to a box in source pixels.
-b, --background <color> Paint the viewport background.
-t, --theme <theme> light or dark; defaults to the source theme, or dark for kitty and light otherwise.
--font <file> Load an extra font face; repeat for additional weights and styles. Family, weight, and style come from the font metadata.
--title <text> Set the SVG, PDF, or PPTX document title.
--id-prefix <name> Prefix SVG definition IDs, default gum.
--precision <digits|full> Output decimal places from 0 to 100, or full; default 10.
--text-mode <path|live|mixed> Text and math in SVG/PDF/PPTX. mixed keeps prose live and math outlined. Defaults to path for SVG, live for PDF, and mixed for PPTX. PNG, kitty, and MP4 always use paths.
--stats Print layout counters to stderr.
-h, --help Show command help.

Omit the input file or use - to read stdin. A bare element is wrapped in Page. -W / --width and -H / --height are independent pixel overrides; -h remains the help shortcut. With neither override, gum offers 640 × 480 pixels so unsized canvases can render. This is an advisory budget: explicit source sizes still win, short content hugs, and tall documents can grow vertically. With either override, the other axis retains source sizing or hugs content, allowing -W 320 to reflow a document and an aspect ratio to determine a figure's height. Zero is a valid viewport dimension for SVG, tree, and JSON; PNG, PDF, and kitty require positive dimensions. The sampling ratio must be positive and changes raster sampling without changing layout. Raster dimensions round up to whole pixels.

Use --select 100,50,200,100 --ratio 3 to crop a 200-by-100-pixel region starting at (100, 50) and render it at 600 by 300 pixels. Coordinates are in the laid-out source viewport, measured from the top-left. Selection applies to PNG and kitty output; other formats report an error. Fractional coordinates and regions extending outside the image are supported.

An explicit format takes precedence over the output filename. Otherwise the output extension selects the format. For a file or stdin, stdout defaults to kitty, including when redirected or piped; directories and multiple files default to PDF. Use -f svg for SVG on stdout, -f pdf for binary PDF on stdout, or -o figure.svg / -o figure.png / -o figure.pdf to select a file format automatically. Kitty output displays inline in terminals that support the kitty graphics protocol and ends with a newline. An explicit -f kitty or an output filename ending in .kitty writes the same graphics sequence.

Rendering defaults to dark for kitty and light for SVG, PNG, PDF, PPTX, tree, and JSON. An explicit root <Page theme="light|dark"> overrides that default, and --theme light|dark overrides the source root theme. Nested themes and explicit colors in JSX still apply. Themes do not specify backgrounds. --background paints a backdrop at render time; omit it for transparency. Explicit backgrounds in JSX still apply and paint over the render backdrop. See Themes for palettes and semantic paints.

PNG and kitty render fragments through @gum-jsx/png and tiny-skia WebAssembly. Outlined text, math, shapes, and embedded PNGs need no native addons or install scripts. --text-mode live preserves text and math as live SVG text. SVG viewers need matching fonts; the option does not embed or install them in SVG. PNG and kitty always request glyph outlines regardless of that flag. Emoji without outlines cannot be rasterized; export SVG to display them in a browser with suitable fonts. --background also fills any area of a PNG crop outside the figure viewport.

Load custom font files with repeatable --font options:

gum figure.jsx --font ./Inter-Regular.ttf --font ./Inter-Bold.ttf -o figure.svg

Select the inherited family in the source:

<Page font-family="Inter">
  <Text>Text inherits the document's font.</Text>
</Page>

Font paths resolve from the directory where you run the command. Supply an individual face such as a TTF or OTF, not a font collection. Gum reads the family name from the file, preferring its typographic family so extended weights stay grouped. Register each desired weight and style separately; variable fonts use their default instance. An existing family/weight/style registration is replaced by the last supplied face with that identity.

Loading a font makes it available to font-family="Inter" in JSX without changing the default. Plex and math fonts remain available. font-family also accepts bundled families, such as "IBM Plex Mono"; nested font choices override inherited values. Font loading and props apply to figures, decks, video previews, and MP4 output. Live SVG and PPTX still require matching fonts in the viewer.

Select a loaded math font separately from the prose family:

gum figure.jsx --font ./MyMath-Regular.otf -o figure.png
<Page math-font="My Math">
  <Latex>x^2 + y^2</Latex>
</Page>

The math provider uses the selected font for ordinary glyphs, including Unicode math italic and double-struck alphabets where available. Missing glyphs, other styled alphabets, and large size-font symbols use bundled KaTeX faces. KaTeX layout parameters and stretchy constructions remain in use; OpenType MATH tables are not read. The math-font prop inherits through figures, decks, and video.

PDF uses @gum-jsx/pdf. It writes vector pages sized to their viewports at 96 pixels per inch (0.75 PDF points per pixel). --ratio and --id-prefix do not affect PDF output. Text and math glyphs default to selectable native text with embedded font subsets shared across all pages. --text-mode path exports outlines instead. Math decorations remain vector geometry, and copying formulas does not reconstruct TeX. Debug overlays are omitted. --title sets PDF document metadata. Named, hex, RGB, and HSL colors are supported; unsupported paint expressions fail with an error. See the PDF API documentation for format limits.

PPTX uses @gum-jsx/pptx to write native vector shapes and embedded PNG pictures. Use gum figure.jsx -o figure.pptx or gum slides/ -o talk.pptx. The default mixed mode uses selectable, editable prose with fixed line breaks and styled runs, plus outlined math that needs no installed math fonts. Prose fonts are referenced without embedding; the viewer needs matching fonts. Use --text-mode live for editable math glyphs too, or --text-mode path for all outlines. Slides have a white base, must share one size, and must measure 96–5376 pixels per side at the default physical scale. Fragment clips are ignored, so content outside them is exported in full. Skewed images, reflected/skewed/nonuniformly scaled live text, nonuniformly transformed strokes, and combined fill/stroke opacity are deferred and produce clear errors. --ratio, --precision, and --id-prefix do not affect PPTX. See the PPTX API.

--precision sets the decimal places used in SVG, PDF, and tree numeric output; PNG and kitty rendering use the full layout geometry. Choose an integer from 0 to 100, or full for unrounded JavaScript number strings. It does not change layout geometry.

Errors go to stderr and exit with status 1. --stats writes layout counters as JSON to stderr, one line per rendered page.

MP4 animations

Return a top-level Video component from a JSX source. Supply size, fps, duration in seconds, and a synchronous frame generator:

return <Video
  size={[640, 360]}
  fps={30}
  duration={3}
  frame={({ time }) => (
    <Box padding={em(1)}>
      <Text>{time.toFixed(2)} seconds</Text>
    </Box>
  )}
/>

Alternatively, pass frames as children:

return (
  <Video size={[640, 360]} fps={2}>
    <Page background="red" />
    <Page background="blue" />
  </Video>
)

Children can also be an array, as in <Video size={[640, 360]} fps={2}>{frames}</Video>. Each element occupies one frame; duration is the child count divided by fps, so omit duration and frame when supplying children. At least one frame is required. Both forms accept an optional background. Core, math, map, and plugin bindings remain available, plus Video, lerp, progress, and ease_in_out. The original generator-based video objects remain supported.

gum orbit.jsx -o orbit.mp4                # Export the complete animation
gum orbit.jsx -f mp4 > orbit.mp4          # Stream MP4 to stdout
gum orbit.jsx --time 1.5                 # Kitty preview (defaults to time 0)
gum orbit.jsx --time 1.5 -o frame.png     # Save a frame
gum orbit.jsx -o orbit.mp4 --qp 16        # Higher quality (default quantizer: 18)

Frame previews also support SVG, PDF, PPTX, tree, and JSON output. --time selects floor(time * fps) and must be nonnegative and less than the duration. Omit it when exporting MP4. -W, -H, --theme, and --background override the video's viewport and appearance; previews use the same source dimensions and background as the movie. MP4 requires even dimensions from 2 to 4096.

Encoding uses the bundled @gum-jsx/mp4 Rust/WASM backend; FFmpeg is not needed. --qp accepts integers 10–51, with lower values giving larger, higher-quality files. Output is H.264 in fragmented MP4, with no audio. Directory and multi-file inputs remain PDF/PPTX only. MP4 does not support --ratio, --select, or --stats; use -W/-H for video dimensions. File exports preserve an existing destination on failure; stdout may contain a partial stream if rendering fails.

See the MP4 package for the source format, encoder API, and limitations.

PDF and PowerPoint decks

Write a complete presentation in one JSX file with Document and Page:

<Document title="My talk" width={px(960)} height={px(540)} font-size={px(28)}>
  <Page>
    <Slide width="fill" height="fill" title="Introduction">
      <Text>One source file can contain the whole presentation.</Text>
    </Slide>
  </Page>
  <Page background={slate} color={white}>
    <Slide width="fill" height="fill" title="Next steps">
      <Text>Each page can override the document's shared defaults.</Text>
    </Slide>
  </Page>
</Document>
gum talk.jsx -o talk.pdf
gum talk.jsx -o talk.pptx
gum talk.jsx --page 2 -o second-page.png

Page replaces the former Svg element and retains its sizing and clipping behavior. A standalone Page is sufficient for a single output surface; Document collects Pages with shared dimensions, styles, and a title. Pages lay out independently, and a page's props override document defaults. Slide supplies the title/body composition inside a page.

PDF and PPTX export all pages. SVG, PNG, and kitty require --page for a multi-page document; page numbers start at 1. Tree output inspects every page, and JSON output contains { title, pages }. --page can select a single page for any of these outputs. --title overrides the document title.

Pass JSX files in slide order or one directory containing slides to render a PDF or PPTX:

gum intro.jsx figure.jsx conclusion.jsx -o talk.pdf
gum slides/ -o talk.pdf
gum slides/ -o talk.pptx
gum slides/ > talk.pdf

Directories and multiple files default to PDF and also accept PPTX. PDF pages retain their own viewport sizes; PPTX slides must all have the same size. -W and -H apply to every slide. Files returning Documents contribute all their pages in order. Long content is not automatically split across pages. Directories and stdin cannot be combined with other inputs.

A directory can contain an optional index.json:

{
  "title": "My talk",
  "prelude": "prelude.jsx",
  "slides": ["intro.jsx", "figure.jsx", "conclusion.jsx"]
}

All fields are optional. Without slides, Gum uses the directory's .jsx files in natural filename order (slide_2.jsx before slide_10.jsx), excluding the named prelude. It does not recurse into subdirectories. Manifest paths are relative to the directory. title supplies PDF/PPTX metadata unless --title overrides it.

The prelude contains shared declarations, such as colors, data, and JSX helpers:

const accent = '#369'
function DeckPage({ children }) {
  return (
    <Page width={px(960)} height={px(540)}>
      <Frame padding={px(32)}>
        {children}
      </Frame>
    </Page>
  )
}

Each prelude is evaluated once per command. Its top-level bindings are available to each slide, along with the usual core and math helpers. Slides have separate local declarations and may be bare JSX or JavaScript that returns an element. Manifest and prelude handling applies to directory input. Explicit files or stdin uses ordinary core and math bindings without loading neighboring index.json files. A slide rendered as an individual file must be self-contained. Pass the deck directory to use its prelude.

Libraries and integrations

Use Gum from your own code with the core renderer, math, and maps libraries. Export packages provide PNG, PDF, PowerPoint, and MP4 output.

React bindings embed Gum in React applications. The documentation repository maintains the guides, examples, and agent instructions.

Development

For development work, see the meta repo for instructions. That repository contains this one as a submodule.

Run bun run typecheck and bun run test from this package directory for TypeScript and command integration checks. The workspace commands run checks across all packages.

bun run test packs and installs the npm package, then runs the CLI, docs, and loader suites once under Node, with up to four cases running concurrently. Bun-only plugin tests use the same install. One PNG smoke check compares Node, Bun, and the standalone executable; release archive checks also run. Cases use distinct fixture and output paths within each suite's temporary directory. For a focused source check, use bun test ./test/cli.test.ts -t "test name".

Build the npm package

From this package's directory, run bun run build to generate dist/npm/. npm pack and npm publish run this build automatically through prepack. The package ships this bundle and its assets/notices, plus the README, license, and README artwork. Builds preserve class names used in layout diagnostics and share the Acorn deduplication used by standalone executables.

Run bun test test/package.test.ts to pack the CLI, install it offline in a fresh project with lifecycle scripts disabled, and run the installed-package suite. Node rendering is tested with an empty PATH. Set GUM_NODE_RUNTIME to test a specific Node executable.

Standalone executable

From this package's directory, build gum with Bun 1.4.2 or newer. With no options, the script builds macOS ARM64 and x64, Windows x64, and Linux x64:

bun run standalone:build

The outputs are dist/gum-macos-arm64, dist/gum-macos-x64, dist/gum-windows-x64.exe, and dist/gum-linux-x64. Select one target with --target; optionally override its output path with --outfile:

bun run standalone:build --target bun-linux-x64
bun run standalone:build --target=bun-darwin-arm64 --outfile dist/gum-macos

The executable includes the Bun runtime, core and math fonts, map data, and the PNG and MP4 WebAssembly renderers. Users need no Bun installation or node_modules for rendering. This build produces only gum.

For a local build using the installed Bun runtime, or to test a release executable:

bun run standalone:build --target native
./dist/gum figure.jsx -o figure.png
GUM_STANDALONE_BINARY="$PWD/dist/gum-linux-x64" bun test ./test/package.test.ts

About

Gum graphics CLI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages