PowerPoint
Installed as @ranger/pptx.
Open a .pptx, read it, change it, write it back — draw it to PNG or PDF, and put a Vega chart on it as shapes.
Generated from the Ranger sources that declare the surface — gallery/pptx/api/PptxApi.rgr and gallery/pptx/api/PptxRenderApi.rgr and gallery/pptx/api/PptxChartApi.rgr — so this page cannot describe a method that is not there.
The document
Section titled “The document”import { … } from "@ranger/pptx";Headless: a ZIP reader, an XML parser, the model and the writer. No canvas and no fonts.
A run of text inside a shape, addressed by paragraph and position.
A deck’s text is not a string: it is paragraphs of runs, and a run is the unit that carries a font, a size, a weight and a colour. setText on a shape replaces the lot and keeps the FIRST run’s formatting, because that is what someone filling in a template means. Reach for this class when that is not what you mean — when the second half of a sentence has to be red and the first half must not change.
| Call | Gives | What it is for |
|---|---|---|
run.exists |
boolean |
Is this run still there? A handle taken before an edit that removed paragraphs is a handle to nothing, and every writer below checks this first rather than reaching past the end of an array. |
run.text |
string |
|
run.setText(s) |
Run |
|
run.font(family, sizePt) |
Run |
The font this run NAMES. |
run.bold(on) |
Run |
|
run.italic(on) |
Run |
|
run.color(hex) |
Run |
“4472C4” — six hexadecimal digits, with or without a leading ‘#’. |
run.exists
Section titled “run.exists”Is this run still there? A handle taken before an edit that removed paragraphs is a handle to nothing, and every writer below checks this first rather than reaching past the end of an array.
run.font(family, sizePt)
Section titled “run.font(family, sizePt)”The font this run NAMES. It is not necessarily the font it is drawn in — a renderer substitutes what it has — but it is what the file says and what PowerPoint will use.
One shape on one slide: a box, a picture, a group, a text placeholder.
index addresses the slide’s TOP-LEVEL shapes. A shape inside a group is not reachable from here, and that is a stated limit rather than an oversight: a group’s children live in the group’s own coordinate space, and handing them out through the same handle as a slide’s shapes would make at(x y) mean two different things depending on where the shape came from.
| Call | Gives | What it is for |
|---|---|---|
shape.exists |
boolean |
|
shape.name |
string |
|
shape.setName(s) |
Shape |
|
shape.preset |
string |
The preset geometry: “rect”, “ellipse”, “roundRect”, “star5” — one of the 187 names ECMA-376 defines. |
shape.setPreset(name) |
Shape |
|
shape.x |
number |
|
shape.y |
number |
|
shape.width |
number |
|
shape.height |
number |
|
shape.rotation |
number |
|
shape.at(nx, ny) |
Shape |
Points, from the top left of the slide. |
shape.size(w, h) |
Shape |
|
shape.rotate(deg) |
Shape |
Degrees clockwise. |
shape.fill(hex) |
Shape |
|
shape.noFill() |
Shape |
|
shape.line(hex, widthPt) |
Shape |
|
shape.noLine() |
Shape |
|
shape.text |
string |
Every character in the shape, paragraphs joined by a newline. |
shape.addClass(name) |
Shape |
Give this shape a class a stylesheet can address. |
shape.removeClass(name) |
Shape |
|
shape.hasClass(name) |
boolean |
|
shape.setStyleId(id) |
Shape |
The id a stylesheet addresses with #name. |
shape.styleId() |
string |
|
shape.style(name, value) |
Shape |
One property, stated here rather than in a sheet. |
shape.setText(s) |
Shape |
Replace all of the text, keeping the first run’s formatting. |
shape.paragraphCount |
number |
|
shape.runCount(para) |
number |
|
shape.run(para, index) |
Run |
|
shape.addRun(para, text) |
Run |
Add a run to the end of a paragraph, formatted like the one before it. |
shape.align(how) |
Shape |
Horizontal alignment of every paragraph: “l”, “ctr”, “r”, “just”. |
shape.preset
Section titled “shape.preset”The preset geometry: “rect”, “ellipse”, “roundRect”, “star5” — one of the 187 names ECMA-376 defines. gallery/office/geom/assets/presets.txt is the catalogue, and gallery/pptx/docs/PRESET_SHAPES.md lists them.
shape.at(nx, ny)
Section titled “shape.at(nx, ny)”Points, from the top left of the slide. A deck stores English Metric Units — 12700 to the point — and nothing above this line ever sees one.
hasXfrm is set here because a shape that states no transform inherits its box from the layout: moving such a shape without saying it now has its own geometry writes a position the file does not carry, and the shape reopens where the layout put it.
shape.rotate(deg)
Section titled “shape.rotate(deg)”Degrees clockwise. The file stores sixtieths of a thousandth of a degree; the writer converts, and a negative angle wraps rather than being written as a negative number, which PowerPoint does not read.
shape.text
Section titled “shape.text”Every character in the shape, paragraphs joined by a newline. This is what a search reads; it is not what a writer should round-trip, because the runs and their formatting are not in it.
A GROUP ANSWERS FOR ITS CHILDREN. This used to stop at the group and return nothing, because a group carries no text body of its own — so a deck whose text boxes had been grouped, which is most decks anybody has arranged, read back as blank. Nothing failed; the search index was simply empty and there was no way to tell that from a deck with no words in it.
shape.addClass(name)
Section titled “shape.addClass(name)”Give this shape a class a stylesheet can address.
Chainable and repeatable: addClass("card").addClass("wide") carries both, and a class added twice is carried once.
shape.setStyleId(id)
Section titled “shape.setStyleId(id)”The id a stylesheet addresses with #name.
NOT setName. That is the PowerPoint object name — what an author sees in the selection pane and renames without thinking about styling — and conflating the two would mean a rename silently restyles the box. Set both when you want both.
shape.style(name, value)
Section titled “shape.style(name, value)”One property, stated here rather than in a sheet.
This is CSS’s inline style and it behaves like it: nothing in any sheet can override it, !important included. The fluent setters beside it — fill, noLine, run(0,0).bold() — write the model directly and are not affected by the cascade either, so the two spellings agree.
shape.setText(s)
Section titled “shape.setText(s)”Replace all of the text, keeping the first run’s formatting.
A newline starts a new paragraph, and each paragraph keeps the FIRST paragraph’s properties — alignment, bullet, indent. Filling a template is what this is for: the box was styled by whoever made the template, and a caller who replaces the words means to keep that styling. Building text whose parts differ from one another is runAt and addRun.
shape.addRun(para, text)
Section titled “shape.addRun(para, text)”Add a run to the end of a paragraph, formatted like the one before it. A shape with no text at all gets its first paragraph here.
One slide.
| Call | Gives | What it is for |
|---|---|---|
slide.exists |
boolean |
|
slide.width |
number |
|
slide.height |
number |
|
slide.shapeCount |
number |
|
slide.shape(i) |
Shape |
|
slide.shapeNamed(name) |
Shape |
The first shape whose name matches, or a handle that says it does not exist. |
slide.text |
string |
Every character on the slide, shape by shape in the order they are drawn. |
slide.addClass(name) |
Slide |
Give this slide a class a stylesheet can address: slide.review \{ … \}. |
slide.setStyleId(id) |
Slide |
The id a stylesheet addresses with #name. |
slide.style(name, value) |
Slide |
One property, stated here rather than in a sheet. |
slide.addTextBox(x, y, w, h, text) |
Shape |
|
slide.addShape(preset, x, y, w, h) |
Shape |
|
slide.removeShape(i) |
boolean |
|
slide.background(hex) |
Slide |
slide.shapeNamed(name)
Section titled “slide.shapeNamed(name)”The first shape whose name matches, or a handle that says it does not exist. A template names its boxes — “Title”, “Subtitle”, “Footer” — and filling one in by name is what most callers want; by index is what breaks the moment somebody reorders the slide in PowerPoint.
slide.setStyleId(id)
Section titled “slide.setStyleId(id)”The id a stylesheet addresses with #name. See PptxShapeRef.setStyleId for why this is separate from anything the file calls the slide.
The deck.
Two ways out, and the difference matters more than it looks:
save() writes over the package the deck was OPENED from. Every part nobody here models — animations, embedded workbooks, custom XML, the parts a future version of PowerPoint adds — is copied through byte for byte, and only the slides marked dirty are rewritten. This is what “edit a document” should mean. saveNew() writes a package built entirely from the model. Everything not modelled is gone. It is the right answer for a deck this API CREATED, and the wrong one for a deck it opened.
save() on a created deck falls back to saveNew(), because there is no package to write over — so the common case needs no thought.
| Call | Gives | What it is for |
|---|---|---|
deck.addStyleSheet(text) |
Deck |
Add CSS over the deck. |
deck.applyStyles() |
Deck |
Resolve the sheet over every slide, shape, paragraph and run. |
deck.styleWarnings() |
string[] |
Property names the sheet used that a slide cannot honour, and the selectors this layer could not read. |
deck.slideCount |
number |
|
deck.slide(i) |
Slide |
|
deck.width |
number |
Points. |
deck.height |
number |
|
deck.setSize(w, h) |
Deck |
|
deck.addSlide() |
Slide |
|
deck.removeSlide(i) |
boolean |
|
deck.text |
string |
Every character in the deck, slide by slide. |
deck.save() |
Uint8Array |
|
deck.saveNew() |
Uint8Array |
deck.addStyleSheet(text)
Section titled “deck.addStyleSheet(text)”Add CSS over the deck.
The rules are resolved when the deck is SAVED — or when applyStyles is called — rather than here, because a sheet is declarative and the boxes it describes are usually created after it. Called more than once, the later rules simply come later in source order, which is what a second sheet should mean.
The selector subset is small on purpose: .class, #id, an element name (slide, shape, textBox, picture, group, table, chart, paragraph, run), the two combined, and the descendant combinator. font-family, font-size, font-weight, font-style and color inherit down to the runs; fill, stroke and stroke-width do not. Anything a slide cannot honour — display: flex and the rest of the layout properties — is reported through styleWarnings rather than ignored in silence.
deck.applyStyles()
Section titled “deck.applyStyles()”Resolve the sheet over every slide, shape, paragraph and run.
Called for you by save and saveNew, and by the renderer before it draws — so the only reason to call it yourself is to READ a styled value back off the model before doing either.
Idempotent: the cascade computes the same answer from the same sheet and the same classes, so calling it twice sets the same values twice.
deck.styleWarnings()
Section titled “deck.styleWarnings()”Property names the sheet used that a slide cannot honour, and the selectors this layer could not read. Empty when everything landed.
The front door.
| Call | Gives | What it is for |
|---|---|---|
Pptx.version |
string |
The version of this SURFACE, not of the repository. |
Pptx.open(bytes) |
Deck |
Open a .pptx from its bytes. |
Pptx.create() |
Deck |
A deck with nothing in it: one 16:9 page size, no slides. |
Pptx.version
Section titled “Pptx.version”The version of this SURFACE, not of the repository. It changes when a method changes meaning, so a caller can tell what it is talking to.
A function rather than an sdef: the compiler inlines a static constant at its use sites and emits nothing for it, so a constant no Ranger code reads does not exist in the compiled module at all — Pptx.version came out undefined in JavaScript and nothing had failed to build.
Pptx.open(bytes)
Section titled “Pptx.open(bytes)”Open a .pptx from its bytes.
Bytes rather than a path on purpose: this has to work in a browser, where there is no file system, and in a build step, where the deck arrived over HTTP. A caller with a path reads it and hands the bytes over.
Pptx.create()
Section titled “Pptx.create()”A deck with nothing in it: one 16:9 page size, no slides. addSlide is the next call.
The two programs below call this API in the page. Edit the
JavaScript and press Run. The viewer opens the .pptx the
code produced — not a preview of the model in memory.
Drawing
Section titled “Drawing”import { … } from "@ranger/pptx/render";PNG and PDF. A separate entry point because its bundle is five times the size.
Renderer
Section titled “Renderer”Draws decks. Give it fonts, then ask for a PNG or a PDF.
It is built on PptxView — the editor’s own painter — so a PNG from here and a screenshot of the editor are the same picture rather than two renderings that agree until they do not.
| Call | Gives | What it is for |
|---|---|---|
renderer.useFontDir(dir) |
Renderer |
Every face in a directory laid out the way this repository’s font assets are: the four Open Sans styles as the text family, then the emoji, bullet and Arabic faces joined to the fallback pool. |
renderer.addFont(family, data) |
Renderer |
One face, named, from its bytes. |
renderer.addFace(data) |
Renderer |
A face that joins the per-codepoint fallback pool WITHOUT becoming a family anything is drawn in. |
Renderer.standardFaces() |
string[] |
The faces this repository ships and every editor here loads, in the order they are loaded — which is the order the fallback walk takes them in. |
renderer.usePresets(text) |
Renderer |
The 187 preset geometries, as the catalogue text. |
renderer.toPng(deck, index, scale) |
Uint8Array |
— PNG –––––––––––––––––––––––––––––––––– |
renderer.pngWidth() |
number |
|
renderer.pngHeight() |
number |
|
renderer.toPdf(deck, index) |
Uint8Array |
— PDF –––––––––––––––––––––––––––––––––– |
renderer.toPdfDeck(deck) |
Uint8Array |
— the whole deck, one file ———————————————– |
renderer.useFontDir(dir)
Section titled “renderer.useFontDir(dir)”Every face in a directory laid out the way this repository’s font assets are: the four Open Sans styles as the text family, then the emoji, bullet and Arabic faces joined to the fallback pool.
gallery/pdf_writer/assets/fonts is that directory.
renderer.addFont(family, data)
Section titled “renderer.addFont(family, data)”One face, named, from its bytes. The first call for a family makes that family; later calls with the same name add its bold, italic and bold italic — FontManager keys a face by family AND style.
renderer.addFace(data)
Section titled “renderer.addFace(data)”A face that joins the per-codepoint fallback pool WITHOUT becoming a family anything is drawn in. This is how an emoji face is added: text stays in the text font and a 😀 is answered from here.
Renderer.standardFaces()
Section titled “Renderer.standardFaces()”The faces this repository ships and every editor here loads, in the order they are loaded — which is the order the fallback walk takes them in. Handed out so a browser can fetch exactly this list and be sure it measures what the desktop measures.
renderer.usePresets(text)
Section titled “renderer.usePresets(text)”The 187 preset geometries, as the catalogue text. Without it the painter falls back to the hand-written table and every shape the specification defines and nobody typed in comes out as a rectangle. gallery/office/geom/assets/presets.txt is the file.
renderer.toPng(deck, index, scale)
Section titled “renderer.toPng(deck, index, scale)”— PNG ––––––––––––––––––––––––––––––––––
scale is a multiplier on 96 dots per inch, which is what a slide point means on a screen: 1.0 gives a 960pt-wide deck at 1280 pixels, 2.0 at 2560. It is not a pixel width on purpose — a deck’s pages can differ in size, and a caller asking for “twice as sharp” means that whatever the page is.
renderer.toPdf(deck, index)
Section titled “renderer.toPdf(deck, index)”— PDF ––––––––––––––––––––––––––––––––––
One slide, as a vector page at the slide’s own size — a 960x540pt deck becomes a 960x540pt page, because a PDF unit IS a typographic point and a deck already states its size in them. Nothing is rasterized: the text is text a reader can select and search, and the shapes are path operators.
The route is DISPLAY LIST, not the element tree slideToEvg builds. That distinction is the whole reason this works: the display list is what the software canvas, the WebGL backend and the browser all paint, so it is where every fidelity fix in this gallery has gone — the 187 preset geometries, the bidirectional reordering, the resolved fonts, the group rotation. The element tree is a coarse approximation that predates all of it. Printing the approximation would give a PDF that disagreed with the screen in exactly the places the screen was carefully made right.
EVGListToElements converts one to the other so the PDF writer needs no second input format, and it is deliberately not pptx-specific: the .docx reader and the spreadsheet emit the same display lists.
renderer.toPdfDeck(deck)
Section titled “renderer.toPdfDeck(deck)”— the whole deck, one file ———————————————–
Every slide becomes a page of one PDF, in order. This is what a deck IS to anyone who wants to print it or send it, and doing it slide by slide and stitching afterwards would embed the fonts once per page — the same face, subsetted separately, as many times as there are slides.
A deck whose slides differ in size gets the FIRST slide’s size for every page, because a section states one page size for all the pages under it. That is a real limit and it is stated rather than hidden: PowerPoint has one size per presentation, so a deck that hits it was assembled from two.
Charts
Section titled “Charts”import { … } from "@ranger/pptx/chart";A Vega or Vega-Lite specification onto a slide, as DrawingML shapes rather than as a picture.
Puts a Vega or Vega-Lite chart on a slide.
A failure is a value here, exactly as in PptxApi: a specification with a typo in it leaves ok false and says why in error, rather than throwing through a compiled boundary that has no exceptions to throw.
| Call | Gives | What it is for |
|---|---|---|
chart.font(family) |
Chart |
What “sans-serif” is called on this deck. |
chart.config(json) |
Chart |
Vega-Lite config for every chart this object draws. |
chart.curveSteps(n) |
Chart |
How finely a curve is subdivided — an area’s top edge, a pie wedge, a smoothed line. |
chart.addTo(slide, spec, x, y, w, h) |
PptxShapeRef |
Compile spec and put it on slide, inside the box, in points. |
chart.font(family)
Section titled “chart.font(family)”What “sans-serif” is called on this deck.
Vega names a CSS family. A slide names a font. Writing “sans-serif” into a run leaves every reader to pick, which is the one outcome that makes two people looking at the same deck see different charts. The default is Calibri; name the deck’s own font here.
chart.config(json)
Section titled “chart.config(json)”Vega-Lite config for every chart this object draws.
A deck of six charts otherwise states the same thing six times — the same axis colours, the same absent domain line, the same view with no border round it — and the interesting half of each specification, which is its DATA, ends up buried in the half that never changes. Say it once here and the specifications carry only what differs.
A chart’s own config still wins, key by key and one level down: setting view on one chart replaces the view block and leaves the axis block from here standing, rather than dropping every default it also wanted.
chart.curveSteps(n)
Section titled “chart.curveSteps(n)”How finely a curve is subdivided — an area’s top edge, a pie wedge, a smoothed line. Sixteen segments is under a tenth of a point of error at chart size; raise it for a chart that fills the slide, lower it for a sparkline in a table cell.
chart.addTo(slide, spec, x, y, w, h)
Section titled “chart.addTo(slide, spec, x, y, w, h)”Compile spec and put it on slide, inside the box, in points.
The chart keeps the aspect ratio the specification asked for and is centred in the box: a 200×200 specification in a 400×200 box is a square chart in the middle of it rather than a stretched one. State the size in the specification and the room in the box.
The handle that comes back is the GROUP. Moving it moves the chart; ungrouping it in PowerPoint leaves the bars and the labels behind as shapes, which is the point of not shipping a picture.
Source
Section titled “Source”Licence: AGPL-3.0-or-later. This page quotes the comments in those files.
Licence: AGPL-3.0-or-later · commit f323fd4 · Ranger language documentation (MIT)