pptxrs
v0.1.11
Published
Create, read, modify, and export .pptx files — Rust/WASM npm library for Node.js
Maintainers
Readme
pptxrs
Created by Rafael Costa - @rcosta02.
Performant Rust/WASM library for creating, reading, and modifying PowerPoint (.pptx) files in Node.js — with text measurement and JSON interchange.
Why pptxrs over PptxGenJS?
- Import existing files — open any
.pptx, read its elements, modify them, re-export. - Text measurement — get the exact rendered height and width of a text string (using real font metrics) before writing it to a slide, so you can position other elements relative to it.
- JSON interchange — serialize a full presentation to/from JSON for storage, diffing, or templating.
- Performance — the core runs in Rust compiled to WASM; no native dependencies.
Installation
npm install pptxrsRequires Node.js ≥ 16.
Quick start
const { Presentation } = require("pptxrs");
const pres = new Presentation({ layout: "LAYOUT_16x9", title: "My Deck" });
const slide = pres.addSlide();
const title = slide.addText("Hello, pptxrs!", {
x: 96, // px (96 px = 1 in)
y: 96,
w: 768,
h: 144,
fontSize: 44,
bold: true,
});
console.log(title.width, title.height); // pixels
await pres.writeFile("deck.pptx");Table of contents
- Coordinates
- Creating a presentation
- Adding slides
- Text
- Images
- Shapes
- Tables
- Charts
- Speaker notes
- Slide masters
- Exporting
- Importing existing .pptx files
- Reading element dimensions
- Modifying imported slides
- Updating charts in imported slides
- Updating tables in imported slides
- Text measurement
- JSON interchange
- Full API reference
Coordinates
All position and size values (x, y, w, h) are in pixels (96 DPI). You can also pass a percentage string.
| Value | Meaning |
| ------- | -------------------------- |
| 96 | 96 px = 1 inch |
| "50%" | 50% of the slide dimension |
Standard slide dimensions at 96 DPI:
| Layout | Width (px) | Height (px) |
| ----------------------- | ---------- | ----------- |
| LAYOUT_16x9 (default) | 960 | 540 |
| LAYOUT_4x3 | 960 | 720 |
| LAYOUT_WIDE | 1280 | 720 |
Colors are hex strings without #, e.g. "FF0000" for red.
Reading element dimensions
add* methods and slide.getElements() return SlideElement instances:
// from add* — immediate handle
const el = slide.addText("Hello", { x: 96, y: 96, w: 480, h: 72 });
el.width; // pixels (96 DPI)
el.height; // pixels (96 DPI)
el.x; // x position in pixels
el.y; // y position in pixels
el.widthInches; // inches
el.heightInches; // inches
el.elementType; // "text" | "image" | "shape" | "table" | "chart" | "notes"
el.toJson(); // full element data/options object
// from getElements() — same type
const elements = slide.getElements();
elements[0].width;Creating a presentation
const { Presentation } = require("pptxrs");
const pres = new Presentation({
layout: "LAYOUT_16x9", // 'LAYOUT_4x3' | 'LAYOUT_WIDE'
title: "Q3 Results",
author: "Jane Smith",
company: "Acme Corp",
});
// Metadata can also be set as properties
pres.title = "Updated Title";
pres.author = "John Doe";
pres.company = "ACME";
pres.layout = "LAYOUT_4x3";Adding slides
addSlide() returns a Slide that is auto-tracked — no syncSlide needed:
const slide = pres.addSlide();
slide.addText("Slide 1", { x: 96, y: 96, w: 768, h: 96 });
slide.setBackground("F0F4FF");
// done — write whenever you're readyTo use a named slide master:
const slide = pres.addSlide("MASTER_BRAND");
slide.addText("Content here", { x: 96, y: 192, w: 768, h: 288 });A callback form is also accepted (useful when you want to define the slide inline):
pres.addSlide(null, (slide) => {
slide.addText("Slide 1", { x: 96, y: 96, w: 768, h: 96 });
});Text
Plain text
slide.addText("Hello world", {
x: 96,
y: 96,
w: 768,
h: 144,
// Font
fontSize: 24, // points
fontFace: "Calibri",
bold: true,
italic: false,
underline: true,
strike: "sngStrike", // 'sngStrike' | 'dblStrike'
color: "4472C4", // hex
highlight: "FFFF00", // hex
// Alignment
align: "center", // 'left' | 'center' | 'right'
valign: "middle", // 'top' | 'middle' | 'bottom'
// Spacing
lineSpacingMultiple: 1.5,
charSpacing: 2,
paraSpaceBefore: 6,
paraSpaceAfter: 6,
// Box behaviour
wrap: true,
autoFit: true, // shrink text to fit box
fit: "shrink", // 'none' | 'shrink' | 'resize'
margin: 0.1, // or [top, right, bottom, left] in inches
// Effects
shadow: { type: "outer", angle: 45, blur: 4, color: "000000", opacity: 0.5 },
glow: { size: 8, opacity: 0.4, color: "4472C4" },
// Hyperlink
hyperlink: { url: "https://example.com", tooltip: "Visit site" },
// or link to slide number:
hyperlink: { slide: 3 },
// RTL / language
rtlMode: false,
lang: "en-US",
});Mixed formatting (TextRun array)
Pass an array of runs to mix styles within one text box:
slide.addText(
[
{ text: "Normal text, " },
{ text: "bold red, ", options: { bold: true, color: "FF0000" } },
{ text: "italic blue.", options: { italic: true, color: "0070C0" } },
],
{ x: 96, y: 96, w: 768, h: 96, fontSize: 18 },
);Bullet lists
slide.addText(
[
{ text: "First item", options: { bullet: true } },
{ text: "Second item", options: { bullet: true } },
{
text: "Numbered",
options: { bullet: { type: "number", style: "arabicPeriod" } },
},
{
text: "Custom char",
options: { bullet: { code: "2713", color: "70AD47" } },
},
],
{ x: 96, y: 96, w: 768, h: 384, fontSize: 18 },
);Bullet indent levels (1–32):
slide.addText(
[
{ text: "Level 1", options: { bullet: true, indentLevel: 1 } },
{ text: "Level 2", options: { bullet: true, indentLevel: 2 } },
],
{ x: 96, y: 96, w: 768, h: 288 },
);Superscript / subscript
slide.addText(
[
{ text: "E = mc" },
{ text: "2", options: { superscript: true, fontSize: 12 } },
],
{ x: 96, y: 96, w: 384, h: 96, fontSize: 24 },
);Images
Images can be loaded from the filesystem (auto-resolved) or passed as base64.
// From file path (Node.js — resolved automatically)
slide.addImage({
path: "./assets/logo.png",
x: 48,
y: 48,
w: 192,
h: 96,
});
// From base64
const imgData = fs.readFileSync("./photo.jpg").toString("base64");
slide.addImage({
data: imgData,
x: 288,
y: 96,
w: 384,
h: 288,
});
// Sizing modes
slide.addImage({
path: "./bg.jpg",
x: 0,
y: 0,
w: 960,
h: 540,
sizing: { type: "cover" }, // 'contain' | 'cover' | 'crop'
});
// Effects
slide.addImage({
path: "./photo.png",
x: 96,
y: 96,
w: 288,
h: 288,
rotate: 15,
flipH: true,
rounding: true, // circular crop
transparency: 20, // 0–100
shadow: { type: "outer", angle: 45, blur: 6, color: "000000", opacity: 0.4 },
hyperlink: { url: "https://example.com" },
altText: "Company logo",
});Shapes
shapeType is a string matching PowerPoint preset geometry names.
slide.addShape("rect", {
x: 96,
y: 96,
w: 288,
h: 192,
fill: { color: "4472C4", transparency: 20 },
line: { color: "002060", width: 2, dashType: "dash" },
shadow: { type: "outer", angle: 45, blur: 4, color: "000000", opacity: 0.3 },
rotate: 10,
rectRadius: 0.1, // corner rounding (for roundRect)
});
// Shape with text inside
slide.addShape("roundRect", {
x: 384,
y: 96,
w: 384,
h: 192,
fill: { color: "ED7D31" },
text: "Click me",
fontSize: 20,
bold: true,
color: "FFFFFF",
align: "center",
valign: "middle",
hyperlink: { url: "https://example.com" },
});Common shape types:
| Category | Values |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Basic | rect roundRect ellipse triangle rightTriangle diamond |
| Polygons | pentagon hexagon heptagon octagon |
| Stars | star4 star5 star6 star7 star8 star10 star12 star16 star24 star32 |
| Arrows | rightArrow leftArrow upArrow downArrow leftRightArrow upDownArrow bentArrow uturnArrow curvedRightArrow curvedLeftArrow |
| Callouts | callout1 callout2 callout3 |
| Symbols | heart cloud sun moon lightningBolt smileyFace |
| Lines | line arc |
| Math | mathPlus mathMinus mathMultiply mathDivide mathEqual mathNotEqual |
| Misc | donut pie blockArc ribbon ribbon2 |
Tables
// Simple string data
slide.addTable(
[
["Name", "Department", "Score"],
["Alice", "Engineering", "95"],
["Bob", "Design", "87"],
["Carol", "PM", "91"],
],
{
x: 96,
y: 192,
w: 768,
h: 288,
colW: [288, 288, 192], // column widths in pixels
fontSize: 14,
border: { pt: 1, color: "CCCCCC" },
},
);
// Per-cell formatting
slide.addTable(
[
[
{
text: "Header",
options: {
bold: true,
fill: "4472C4",
color: "FFFFFF",
align: "center",
},
},
{
text: "Value",
options: {
bold: true,
fill: "4472C4",
color: "FFFFFF",
align: "center",
},
},
],
["Row 1", "100"],
["Row 2", "200"],
],
{ x: 96, y: 192, w: 576, h: 288 },
);
// Cell spanning
slide.addTable(
[
[
{
text: "Merged header",
options: { colspan: 3, align: "center", bold: true },
},
],
["Col A", "Col B", "Col C"],
],
{ x: 96, y: 96, w: 768, h: 192 },
);
// Auto-paging (table continues onto new slides)
slide.addTable(data, {
x: 48,
y: 96,
w: 864,
autoPage: true,
autoPageRepeatHeader: true,
autoPageHeaderRows: 1,
newSlideStartY: 96,
});Charts
Bar / column chart
slide.addChart(
"bar",
[
{
name: "Revenue",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [120, 190, 160, 230],
},
{
name: "Expenses",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [80, 110, 90, 130],
},
],
{
x: 48,
y: 48,
w: 864,
h: 480,
barDir: "col", // 'col' (vertical) | 'bar' (horizontal)
barGrouping: "clustered", // 'clustered' | 'stacked' | 'percentStacked'
showTitle: true,
title: "Quarterly Financials",
titleFontSize: 14,
showLegend: true,
legendPos: "b", // 'b' | 't' | 'l' | 'r' | 'tr'
showValue: true,
dataLabelPosition: "outEnd",
chartColors: ["4472C4", "ED7D31", "A9D18E"],
catAxisTitle: "Quarter",
valAxisTitle: "USD (thousands)",
valAxisMaxVal: 300,
valAxisMajorUnit: 50,
},
);Line chart
slide.addChart(
"line",
[
{
name: "Series A",
labels: ["Jan", "Feb", "Mar", "Apr"],
values: [10, 25, 18, 32],
},
],
{
x: 48,
y: 48,
w: 864,
h: 480,
lineSmooth: true,
lineSize: 2.5,
lineDataSymbol: "circle",
lineDataSymbolSize: 8,
showTitle: true,
title: "Monthly Trend",
},
);Pie / doughnut chart
slide.addChart(
"pie",
[
{
labels: ["North", "South", "East", "West"],
values: [35, 25, 20, 20],
},
],
{
x: 96,
y: 48,
w: 768,
h: 480,
showPercent: true,
showLegend: true,
legendPos: "r",
chartColors: ["4472C4", "ED7D31", "A9D18E", "FFC000"],
},
);Scatter / bubble chart
slide.addChart(
"scatter",
[{ name: "Group A", labels: ["1", "2", "3"], values: [10, 20, 30] }],
{ x: 48, y: 48, w: 864, h: 480 },
);
slide.addChart(
"bubble",
[{ name: "Data", values: [10, 20, 30], sizes: [5, 10, 15] }],
{ x: 48, y: 48, w: 864, h: 480 },
);Combo chart (multiple types)
slide.addComboChart(
["bar", "line"], // first type = primary
[
// Data for 'bar' series
[
{
name: "Revenue",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [120, 190, 160, 230],
},
],
// Data for 'line' series
[
{
name: "Margin %",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [30, 40, 35, 45],
},
],
],
{
x: 48,
y: 48,
w: 864,
h: 480,
secondaryValAxis: true,
},
);Chart types
'area' 'bar' 'bar3d' 'bubble' 'bubble3d' 'doughnut' 'line' 'pie' 'radar' 'scatter'
Speaker notes
slide.addNotes(
"Mention the 30% YoY growth. Pause for questions after this slide.",
);Slide masters
Define a master before adding slides that reference it:
pres.defineSlideMaster({
title: "MASTER_BRAND",
background: { color: "002060" },
objects: [
// Logo in the top-right corner
{
type: "image",
options: { path: "./logo.png", x: 816, y: 10, w: 115, h: 48 },
},
// Footer bar
{
type: "shape",
shapeType: "rect",
options: { x: 0, y: 509, w: 960, h: 31, fill: { color: "4472C4" } },
},
// Footer text
{
type: "text",
text: "Confidential — Acme Corp",
options: {
x: 29,
y: 514,
w: 480,
h: 24,
fontSize: 10,
color: "FFFFFF",
},
},
],
slideNumber: { x: 864, y: 514, w: 58, color: "FFFFFF", align: "right" },
});
const slide = pres.addSlide("MASTER_BRAND");
slide.addText("Branded slide", {
x: 96,
y: 192,
w: 768,
h: 192,
fontSize: 36,
color: "FFFFFF",
});Exporting
Write to file
await pres.writeFile("output.pptx");Get a Buffer (Node.js)
const buf = pres.write("nodebuffer"); // Buffer
fs.writeFileSync("output.pptx", buf);Get a Uint8Array
const bytes = pres.write("uint8array");Get base64
const b64 = pres.write("base64");
// e.g. send as HTTP response:
res.json({ file: b64 });Importing existing .pptx files
const { Presentation } = require("pptxrs");
const fs = require("fs");
// From file
const pres = Presentation.fromBuffer(fs.readFileSync("existing.pptx"));
// From a Uint8Array (e.g. received over HTTP)
const pres2 = Presentation.fromBuffer(new Uint8Array(arrayBuffer));
console.log(pres.layout); // 'LAYOUT_16x9'
const slides = pres.getSlides(); // Slide[]
console.log(slides.length);
for (const slide of slides) {
const elements = slide.getElements();
for (const el of elements) {
console.log(el.elementType, el.width, el.height); // pixels
const data = el.toJson(); // full element with all options
if (data.type === "text") {
console.log(data.text); // string or TextRun[]
console.log(data.options.fontSize); // e.g. 24
console.log(data.options.bold); // true | false
console.log(data.options.color); // e.g. "FF0000"
console.log(data.options.align); // "left" | "center" | "right"
console.log(
data.options.x,
data.options.y,
data.options.w,
data.options.h,
); // pixels
}
if (data.type === "image") {
console.log("image base64 length:", data.options.data?.length);
}
if (data.type === "shape") {
console.log(data.shapeType); // e.g. "rect"
console.log(data.options.fill?.color); // e.g. "4472C4"
}
if (data.type === "table") {
console.log(data.data); // TableCell[][]
console.log(data.options.colW); // column widths in pixels
}
}
}What gets parsed from existing .pptx files
The parser extracts the following from every element type:
| Element | Properties extracted |
| --------- | ------------------------------------------------------------------------------------------------------------- |
| Text | position (x/y/w/h), fontSize, bold, italic, color, align, valign, wrap, multi-run paragraphs |
| Shape | position, shape type (rect, ellipse, roundRect, …), fill color, line width + color, text content if any |
| Image | position, image data as base64 |
| Table | position, column widths (colW), all cell text |
| Chart | position, chart type, all series names, category labels, and data values |
| Slide | background fill color |
Tip:
toJson()on both aPresentationand aSlideElementreturns the complete object including all styling fields — use it to inspect any property.
Reading element dimensions
Every add* method returns a SlideElement handle. slide.getElements() returns the same type. Both work identically whether the slide came from a file, JSON, or was built from scratch.
const { Presentation } = require("pptxrs");
const fs = require("fs");
// ── Example 1: element added to a brand-new slide ─────────────────────────────
const pres = new Presentation();
const slide = pres.addSlide();
const textEl = slide.addText("Hello", { x: 96, y: 96, w: 480, h: 144, fontSize: 32 });
console.log(textEl.elementType); // "text"
console.log(textEl.height); // 144 (pixels)
console.log(textEl.width); // 480 (pixels)
console.log(textEl.heightInches); // 1.5 (inches)
const imgEl = slide.addImage({ data: imgBase64, x: 96, y: 288, w: 192, h: 192 });
console.log(imgEl.elementType); // "image"
console.log(imgEl.height); // 192 (pixels)
console.log(imgEl.width); // 192 (pixels)
// ── Example 2: elements read from an existing .pptx ───────────────────────────
const imported = Presentation.fromBuffer(fs.readFileSync("deck.pptx"));
for (const slide of imported.getSlides()) {
for (const el of slide.getElements()) {
if (el.elementType === "notes") continue; // notes have no dimensions
console.log(
el.elementType,
`${el.width}×${el.height} px`,
`(${el.widthInches.toFixed(2)}×${el.heightInches.toFixed(2)} in)`,
);
}
}Modifying imported slides
After importing, get slides, mutate them, push them back with syncSlide:
const pres = Presentation.fromBuffer(fs.readFileSync("deck.pptx"));
const slides = pres.getSlides();
// Add a watermark to every slide
slides.forEach((slide, i) => {
slide.addText("DRAFT", {
x: 192,
y: 192,
w: 576,
h: 192,
fontSize: 72,
color: "FF0000",
transparency: 70,
rotate: 45,
bold: true,
align: "center",
valign: "middle",
});
pres.syncSlide(i, slide);
});
await pres.writeFile("deck-draft.pptx");Remove a slide:
pres.removeSlide(0); // remove first slideReorder slides by rebuilding:
const slides = pres.getSlides();
// swap slide 0 and slide 1
pres.syncSlide(0, slides[1]);
pres.syncSlide(1, slides[0]);Updating charts in imported slides
slide.updateChart(elementIndex, data) replaces the data for a chart that was parsed from an existing .pptx. All original chart formatting — colors, axis titles, legend position, data labels — is preserved unchanged. Only the series values and labels are updated.
const pres = Presentation.fromBuffer(fs.readFileSync("report.pptx"));
const slides = pres.getSlides();
// Inspect what charts are on slide 0
slides[0].getElements().forEach((el, i) => {
if (el.elementType === "chart") {
const d = el.toJson();
console.log(
i,
d.chartType,
d.data.map((s) => s.name),
);
}
});
// Update the first chart (index 0) with new data
slides[0].updateChart(0, [
{
name: "Revenue",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [142, 198, 175, 251],
},
{
name: "Expenses",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [88, 115, 97, 140],
},
]);
pres.syncSlide(0, slides[0]);
await pres.writeFile("report-updated.pptx");updateChart also works on charts created from scratch with addChart — in that case there is no original formatting to preserve, so the full chart is regenerated with the new data.
Updating tables in imported slides
slide.updateTable(elementIndex, data) replaces the cell content of a table that was parsed from an existing .pptx. All original table formatting — border styles, cell shading, font colors, column widths — is preserved unchanged. Only the text inside each cell is updated.
const pres = Presentation.fromBuffer(fs.readFileSync("report.pptx"));
const slides = pres.getSlides();
// Find all tables on slide 2 and log their current content
slides[2].getElements().forEach((el, i) => {
if (el.elementType === "table") {
console.log(i, el.toJson().data);
}
});
// Replace the content of the table at element index 1
slides[2].updateTable(1, [
["Department", "Headcount", "Avg Score"],
["Engineering", "142", "4.2"],
["Design", "38", "4.5"],
["Operations", "61", "3.9"],
]);
pres.syncSlide(2, slides[2]);
await pres.writeFile("report-updated.pptx");Note: The new data must have the same number of rows and columns as the original table. Changing the grid dimensions is not supported via
updateTable— useaddTableon a new slide instead.
Text measurement
Measure the exact rendered height and width of a string before placing it, so you can stack or position elements dynamically.
You must provide the raw font file so pptxrs can use real font metrics (via HarfBuzz shaping).
const fs = require("fs");
// 1. Register the font
pres.registerFont("Calibri", fs.readFileSync("./fonts/Calibri.ttf"));
// 2. Measure
const metrics = pres.measureText("Hello, world!", {
font: "Calibri",
fontSize: 24, // points
bold: false,
italic: false,
});
// { height: 30.4, width: 148.2, lines: 1, lineHeight: 30.4 }
console.log(metrics.height); // total height in points
console.log(metrics.width); // longest line width in points
console.log(metrics.lines); // number of rendered lines
console.log(metrics.lineHeight); // per-line height in pointsWord-wrap: measure in a constrained box
Pass width in pixels to enable automatic word-wrap:
const m = pres.measureText(longText, {
font: "Calibri",
fontSize: 18,
width: 576, // text box width in pixels (= 6 inches × 96)
});
console.log(m.lines); // how many lines it wraps to
console.log(m.height); // total height; use to size the text boxDynamic layout: stack text boxes
pres.registerFont("Calibri", fs.readFileSync("./fonts/Calibri.ttf"));
const title = "Section Title";
const body = "Here is a longer body paragraph that may wrap…";
const padding = 19; // pixels (~0.2 inch)
const titleMetrics = pres.measureText(title, {
font: "Calibri",
fontSize: 36,
bold: true,
});
// measureText returns points; convert to pixels: points / 72 * 96
const titleH = (titleMetrics.height / 72) * 96;
const bodyMetrics = pres.measureText(body, {
font: "Calibri",
fontSize: 18,
width: 768, // pixels
});
const bodyH = (bodyMetrics.height / 72) * 96;
const slide = pres.addSlide();
slide.addText(title, {
x: 96,
y: 96,
w: 768,
h: titleH + padding,
fontSize: 36,
bold: true,
});
slide.addText(body, {
x: 96,
y: 96 + titleH + padding * 2,
w: 768,
h: bodyH + padding,
fontSize: 18,
wrap: true,
});JSON interchange
Convert a presentation to a plain JSON object and back. The JSON is fully self-contained — images are stored as base64, and when the presentation was imported from an existing .pptx the original ZIP bytes are embedded as sourceZipB64 so that the round-trip fromJson(toJson()) reproduces the file with complete fidelity: slide masters, theme, fonts, chart formatting, table styles, and every other detail from the source file are all preserved.
Serialize
// As a JS object
const json = pres.toJson();
// As a JSON string
const jsonStr = pres.toJsonString();
fs.writeFileSync("deck.json", jsonStr);Deserialize
// From a JS object
const pres2 = Presentation.fromJson(
JSON.parse(fs.readFileSync("deck.json", "utf8")),
);
await pres2.writeFile("rebuilt.pptx");
// fromJson also accepts the live JS object directly
const pres3 = Presentation.fromJson(pres.toJson());Lossless round-trip for imported files
When you call toJson() on a presentation that was loaded via fromBuffer(), the JSON includes a sourceZipB64 field containing the original ZIP encoded as base64. fromJson() detects this field and restores the source ZIP internally, so write() uses the original file as a base and only replaces slides that were actually modified. This means:
// pres-new.pptx and pres2-new.pptx are byte-for-byte identical
const pres = Presentation.fromBuffer(fs.readFileSync("pres.pptx"));
await pres.writeFile("pres-new.pptx");
const pres2 = Presentation.fromJson(pres.toJson());
await pres2.writeFile("pres2-new.pptx"); // identical to pres-new.pptx ✓Presentations built from scratch with new Presentation() do not include sourceZipB64 — the JSON stays lean and the fresh builder is used on the way back.
Schema
interface PresentationJson {
meta: {
title?: string;
author?: string;
company?: string;
layout: string; // 'LAYOUT_16x9' | 'LAYOUT_4x3' | 'LAYOUT_WIDE'
};
slides: {
background?: { color?: string };
master?: string;
elements: SlideElementJson[]; // discriminated union on element.type
}[];
/** Present only when the presentation was loaded from a .pptx file.
* Enables lossless round-trip through toJson() → fromJson(). */
sourceZipB64?: string;
}Each element JSON is a discriminated union:
type SlideElementJson =
| { type: "text"; text: string | TextRun[]; options: TextOptions }
| { type: "image"; options: ImageOptions }
| { type: "shape"; shapeType: string; options: ShapeOptions }
| { type: "table"; data: TableCell[][]; options: TableOptions }
| {
type: "chart";
chartType: string;
data: ChartData[];
options: ChartOptions;
}
| { type: "notes"; text: string };Use cases
Store in a database:
await db.query("INSERT INTO decks (id, data) VALUES ($1, $2)", [
id,
pres.toJsonString(),
]);
// Later:
const row = await db.query("SELECT data FROM decks WHERE id = $1", [id]);
const pres = Presentation.fromJson(JSON.parse(row.data));Generate from a template object:
function buildReport(data) {
const json = {
meta: { title: data.title, layout: "LAYOUT_16x9" },
slides: data.sections.map((section) => ({
elements: [
{
type: "text",
text: section.heading,
options: { x: 96, y: 96, w: 768, h: 96, fontSize: 32, bold: true },
},
{
type: "text",
text: section.body,
options: { x: 96, y: 240, w: 768, h: 288, fontSize: 16 },
},
],
})),
};
return Presentation.fromJson(json);
}Full API reference
new Presentation(options?)
| Option | Type | Default |
| --------- | ---------------------------------------------------- | --------------- |
| layout | 'LAYOUT_16x9' | 'LAYOUT_4x3' | 'LAYOUT_WIDE' | 'LAYOUT_16x9' |
| title | string | — |
| author | string | — |
| company | string | — |
Presentation static methods
| Method | Description |
| ------------------------------ | ----------------------------------------------------- |
| Presentation.fromBuffer(buf) | Import a .pptx file from a Buffer or Uint8Array |
| Presentation.fromJson(json) | Reconstruct from a PresentationJson object |
Presentation instance methods
| Method | Returns | Description |
| ---------------------------- | ------------------------------------ | ------------------------------------------------------- |
| registerFont(name, buf) | this | Register a TTF/OTF font for measureText |
| measureText(text, opts) | TextMetrics | Measure text dimensions in points |
| defineSlideMaster(opts) | this | Define a named slide master |
| addSlide(masterName?, fn?) | Slide | Add a slide; auto-tracked, no syncSlide needed |
| getSlides() | Slide[] | Get all slides |
| syncSlide(index, slide) | this | Push a modified slide back |
| removeSlide(index) | this | Remove slide at index |
| write(outputType?) | Buffer | Uint8Array | string | Export ('nodebuffer' | 'uint8array' | 'base64') |
| writeFile(path) | Promise<void> | Write to disk (Node.js) |
| toJson() | PresentationJson | Serialize to JS object |
| toJsonString() | string | Serialize to JSON string |
Slide instance methods
| Method | Returns | Description |
| ----------------------------------- | -------------- | ----------------------------------------------------------------- |
| addText(text, opts) | SlideElement | Add text or TextRun[] |
| addImage(opts) | SlideElement | Add image (path auto-resolved in Node.js) |
| addShape(type, opts) | SlideElement | Add a preset shape |
| addTable(data, opts?) | SlideElement | Add a table |
| addChart(type, data, opts?) | SlideElement | Add a chart |
| addComboChart(types, data, opts?) | SlideElement | Add a combo chart |
| addNotes(text) | this | Set speaker notes |
| setBackground(hexColor) | this | Set background color |
| updateChart(elementIndex, data) | this | Replace data for an existing chart; preserves all formatting |
| updateTable(elementIndex, data) | this | Replace cell text for an existing table; preserves all formatting |
| getElements() | SlideElement[] | Get all elements with dimension accessors |
SlideElement properties and methods
| Property / Method | Returns | Description |
| ------------------- | ------------------- | ------------------------------------------------------------------------- |
| elementType | string | "text" | "image" | "shape" | "table" | "chart" | "notes" |
| width | number | Width in pixels (96 DPI) |
| height | number | Height in pixels (96 DPI) |
| x | number | X position in pixels (96 DPI) |
| y | number | Y position in pixels (96 DPI) |
| widthInches | number | Width in inches |
| heightInches | number | Height in inches |
| xInches | number | X position in inches |
| yInches | number | Y position in inches |
| toJson() | SlideElementJson | Full element data/options as a plain object |
MeasureOptions
| Field | Type | Description |
| --------------------- | --------- | ------------------------------------------ |
| font | string | Font name (must be registered) |
| fontSize | number | Points |
| bold | boolean | |
| italic | boolean | |
| charSpacing | number | Extra char spacing in points |
| lineSpacingMultiple | number | Multiplier, default 1.0 |
| width | number | Box width in pixels; enables word-wrap |
TextMetrics
| Field | Type | Description |
| ------------ | -------- | --------------------------------- |
| height | number | Total text block height in points |
| width | number | Longest line width in points |
| lines | number | Number of rendered lines |
| lineHeight | number | Per-line height in points |
License
MIT
