Skip to content
OpenPresentationPublic

About

Pure local OPF to PPTX export and PPTX to OPF import tooling

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

OPF PPTX

Catalogs (OPF 0.19): export resolves the document's embedded records and host-registered catalogs (Catalog[], never fetched). Register gallery 2 explicitly for export: import { gallery } from "@openpresentation/gallery"; toPptx(deck, { catalogs: [gallery] }). Bare ids resolve in the document's custom/default records, then the registered default catalog; catalogs.default:false closes that fallback. When import catalogs is omitted, foreign PowerPoint layout type/name mapping uses the default catalog from installed @openpresentation/gallery; theme and colour-scheme recovery still requires explicitly registered catalogs; fromPptx(bytes, { catalogs: [] }) explicitly opts out. An unresolved reference emits core's unresolved-reference diagnostic and uses engine defaults; strictReferences:true refuses the export with OPFPptxError. Native layout tags preserve valid catalog sources and opt-outs, and damaged identity/source metadata emits diagnostics instead of creating invalid catalog groups.

API names (RR-55, unreleased; requires core 0.14, and opf-render 0.14 for the optional peer): toPptx(presentation, { fonts }) takes the fonts handle that opf-render's loadFonts() returns (@openpresentation/opf-render/fonts-node; core's Fonts, an object with at least textMeasurement) and no longer reads a top-level textMeasurement. fromPptx(bytes, { signals: true }) resolves to { presentation, signals } (it was { document, signals }). OPFPptxError carries findings (the error findings of core's format check, or the variable errors) and diagnostics (the layout diagnostics of a layout-overflow), not issues. inventoryPptxTypefaces and checkPptxTypefaces are inventoryTypefaces and checkTypefaces, and checkTypefaces takes { families } where it took { fonts }. Each slide is composed with the options of core's resolveSlideContext, so the deck's resolved font families (slide design, deck design, theme, then the engine default) are the ones the layout is measured in; a reference that matches no record was reported per kind and never refused the export (0.15 replaces this with one unresolved-reference diagnostic and core's engine defaults; see Catalogs above): the default minimal theme and cool-horizon colour scheme were used, and a slide with no layout, or an unknown one, is composed with no layout record (automatic composition, as in the preview; it used to compose against the blank layout, so a layout-less cover is now centred vertically). The package no longer falls back when core lacks a function: it requires core 0.14.

Version 0.12.0 requires @openpresentation/opf ^0.12.0 (composed font sizes on PowerPoint's 0.01 pt grid, hanging wrap whitespace, promoted regions in reading order, right-to-left decks composed mirrored) and exports what that core composes, plus the export and import changes listed in the changelog; use it with opf-render 0.12.0 (optional peer ^0.12.0) and opf-editor 0.11.0 so preview and export resolve one core. Version 0.11.9 exports an SVG image as a native SVG picture over a PNG fallback (rasterized in Node by the optional @openpresentation/opf-render peer or options.svgRasterizer; without one it stays the placeholder), writes a deck footer's first text, date and slide number as native PowerPoint Header & Footer placeholders (output change for decks with a footer; every export now carries the footer placeholders on its master, layout and notes master; see native header and footer) and adds the opt-in fromPptx(bytes, {signals: true}), which also returns the raw per-shape signals of every slide (see Import signals; the option leaves the default import unchanged; core floor ^0.11.4 unchanged, no API removed). Version 0.11.8 re-imports rich text that wraps over several native lines as the one authored payload instead of reporting content-structure-changed (the export records the line count; decks exported by earlier versions import as before; core floor ^0.11.4 unchanged, no API change). Version 0.11.7 requires @openpresentation/opf ^0.11.4 and exports the design fields natively (cover and section logos, header and footer logos, picture bullets, the accent font), writes every chart's text at the size the preview draws, round-trips the slide content structure, sections, extensions, assets and brand images through PPTX provenance (a fresh export of a plain deck re-imports in its authored form, so read a root payload field before blocks[0]), writes slide sections as PowerPoint's section list, and degrades an SVG image to the preview's placeholder instead of aborting; install it with renderer 0.11.9 and editor 0.10.6 so export and preview resolve the same core (no API removed). Version 0.11.6 exports the treemap, histogram, pareto, box-and-whisker, waterfall and funnel charts as native chartex parts by default (toPptx({chartex: 'auto'}); world stays a clustered column with chart-data-adapted; pass chartex: 'fallback' for the previous output) and gives chartex text the deck's label colour and font (no API removed). Version 0.11.5 writes the slide tag run as a theme color reference (a:schemeClr accent1) where the deck theme holds the primary there (no API change; the color is unchanged). Version 0.11.4 re-imports quote payloads (each native quote line carries an OPF_QUOTE_V1 tag) and slide-level image payloads from an unchanged export, writes the theme's a:ea and a:cs only for slots a script font is selected for, and requires core ^0.11.3 (the 70 legacy gallery layout ids and their geometry; install it with renderer 0.11.6 and editor 0.10.4 so export and preview resolve the same core; no API removed). Version 0.11.3 adds the opt-in toPptx({ chartex: 'native' }) export of the seven chartex chart types and always imports chartex charts (no API removed; the default output is unchanged). Version 0.11.2 exports the classic chart types as native chart constructs and writes named and default table colours, text pairs and hyperlink colours as theme references (no API or dependency change; resolved colours are unchanged). Version 0.11.1 exports design.watermark as a native picture (it re-imports) and embeds byte-identical media parts once, with no API or dependency change. Version 0.11.0 requires core ^0.11.2 (cover slides are centered between header and footer furniture) and the optional @openpresentation/opf-render peer ^0.11.0; install them together so export and preview resolve the same core. Version 0.10.0 required core ^0.11.1 and the peer ^0.10.0. It adds native slide-number and date fields, socials, slide-level image treatments, theme color schemes, script-slot fonts and RTL, re-import of design and metadata references, native underline and current-body formatting on import, and UTC-canonical zipDate (see the changelog for the intentional contract changes).

Chart options (RR-35, unreleased): axisTitles, legend and dataLabels on a chart export as native c:title, c:legendPos and c:dLbls (and cx:title, cx:legend, cx:dataLabels on the Office 2016 constructs) and import back; the preview draws the same options. See core docs/chart-options.md for the per-type support table and the chart-option-adapted diagnostic.

Chart emphasis (FA-14, unreleased): chart.highlight exports as native series fills (c:ser/c:spPr, plus c:marker/c:spPr on line, scatter and radar series) and per-point fills (c:dPt: c:spPr on a column or bar, a circular c:marker on a line, the slice fill on a pie or doughnut). The accent is a:schemeClr accent1 when the deck theme holds the primary exactly (a theme edit in PowerPoint recolors it) and an sRGB literal when the contrast adjustment moved it; the muted color is an sRGB literal (a mix of two theme colors is not a scheme color). Labels inside a mark take the color that contrasts with that mark. The highlight is not inferred on import: in full provenance mode it is recorded in the chart's OPF_DATA_V1 record and restored while the chart's cached data is unchanged. Chartex constructs are not highlighted. See core docs/chart-options.md.

Chart and table data (RR-54, unreleased; core docs/chart-table-data.md): chart values pass core chartNumber (a string that is not a plain decimal, such as "12%", "$5" or "(5)", is a gap and one chart-value-not-numeric diagnostic per chart, where the exporter used to strip it to a number). A column number format ({"name": "Revenue", "format": "$#,##0.0"}) is written as the series format code, the data-label and value-axis number formats (the axis takes the first plotted series' format, as the preview does) and the embedded workbook cell formats (Edit Data shows it), on classic and chartex charts. Top-level datasets referenced by {"dataset": "<id>", "fields"?} and chart.mapping export exactly like their inline equivalent; dataset tables and formatted table cells export their display text. With full provenance, OPF_DATASETS_V1 (beside the document tag) and OPF_DATA_V1 (on each chart frame that uses these fields and every table frame) restore the datasets, references, mapping, formats and data.source on import while the native caches (a plain PowerPoint save keeps them), the referenced datasets and the native table evidence are unchanged (tags are limited to 16 MiB and shape-checked; a bad one is reported as invalid-data-provenance); a deck without them maps every series format code that core fromExcelNumberFormat maps back to the column format and reports any other code (chart-number-format-adapted). Full provenance also records inline tables as authored, including scalar numbers and absent styling. Unchanged native cells restore that representation; cell text, formatting, table geometry or theme/style edits retain the observed native cells and report table-data-provenance-changed. references-only and no-provenance tables import displayed text and observed formatting, so scalar types and authored style absence are lossy.

Version 0.9.1 kept core ^0.11.0 and raised the optional @openpresentation/opf-render peer to ^0.9.0 so it coexists with editor 0.8.0. ColorRef / variables on content colors still hex-resolve through core resolveColorRef() before PptxGenJS srgbClr export. Unrecognized run colors such as color:'invalid' still validate and fall back to the theme text color. Native DrawingML schemeClr and theme clrScheme writes, and native p:hf headers/footers, are not in this release. Import still flattens theme colors to hex. Metric, quote and timeline layout placeholders and the corrected text-bullet contract from 0.8.1 are retained.

Unfinished prepared shaping work is preserved in the September 15 roadmap; it is not part of the published runtime.

This checkout supports Node 22 or later (engines.node >=22; CI tests Node 22, 24 and 26). Versions 0.8.0 to 0.12.3 declared 24.x, so npm on Node 22 or 26 silently installed an older release instead (RR-20); upgrade past 0.12.3. Use .nvmrc (Node 24) for local development. Earlier published versions retain their original engine declarations. Browser entrypoints remain browser-safe; native application compatibility is verified separately.

Version 0.8.0 consumes shared heading/scalar/rich line placement, including textRasterPadding, through editable native line boxes without autofit. Complete heading tags retain title/subtitle/tag roles, line ordering and current edited text during import. Source boundary metadata also records hard-line separators, so complete scalar/heading groups recover current native text with spaces, tabs and exact authored line endings. Incomplete, duplicate, ambiguous or bulleted groups fall back to ordinary import with diagnostics. Legacy heading tags retain their prior native-line behavior; arbitrary formatting, nesting and geometry are not reconstructed. See the source contract and limits. Native raster certification remains separate.

Pure local PowerPoint conversion tooling for Open Presentation Format documents. This repo owns the Phase 3 and Phase 4 toolkit lanes: OPF to PPTX export and PPTX to OPF import.

Version 0.7.0's shared-code integration preserves exact source/metadata through guarded native tags and editable lines. XML-forbidden code characters reject export with invalid-code-text, the OPF field path and UTF-16 offset; schema validation alone does not establish XML representability. Native source recovery does not reconstruct formatting, geometry or font theme.

Version 0.8.0 includes shared metric integration. Native alignment and exact tested field recovery are implemented; tab-position and raster fidelity gates remain open.

The native text verifier has completed 24 separately bounded cases on each supported Node runtime against its recorded source baseline. All 72 imports and 96 original-text ink masks per runtime pass; parent-owned temporary fonts are removed after success, failure and timeout controls. Those historical results do not cover subsequent font-selection changes, edited-text reflow, arbitrary native fidelity or the unresolved chart and metric gates.

Version 0.8.0 physical-font selection consumes optional fontFace metadata on accepted text styles. The provider supplies the physical family and its boolean bold/italic style-link flags independently of logical numeric weight. This lets a resolved Roboto SemiBold or ExtraBold face keep its regular legacy-family style rather than requesting a second bold style. Providers without metadata retain the previous numeric-weight behavior; malformed metadata fails with invalid-font-selection. Use core 0.10.0 and renderer 0.8.0. Verification and limits distinguish native selection flags from actual Office paint.

Version 0.8.0 also measures design.contentBox cards through core's padded interior and exports editable rounded frames at their outer allocation. Native tags identify unchanged empty generated frames on reimport, with a content-card-reflow diagnostic: frame appearance and placement are not reconstructed. A name alone never hides a shape; edited, untagged or ambiguous frames use ordinary native import, retaining text or an unsupported-shape description. These source checks do not establish native PowerPoint raster fidelity.

Scope

Version 0.8.0 requires core 0.10.0 and uses renderer 0.8.0 for coordinated preview/font measurement. Quotes and code export their accepted internal lines and styles without another fitting pass. Controlled Windows PowerPoint quote evidence records glyph containment, separation, save/reopen and text reimport against its exact source/font hashes. Native quote import restores the quote payload from an unchanged export (see Quote provenance); it does not restore the original typography or readability policy, and an edited or damaged quote imports as editable text blocks. Native chart geometry and general scalar-text wrapping also remain different from preview; editability and valid reimport do not establish raster equivalence.

  • Package: @openpresentation/opf-pptx
  • Repository: OpenPresentation/opf-pptx
  • License: MIT
  • Compatibility target: @openpresentation/opf
  • Renderer relationship: may use @openpresentation/opf-render for chart rasterization, the PNG fallback of SVG pictures and visual verification
  • Public export API: toPptx(opf, opts)
  • Public import API: fromPptx(buffer, opts) (opts.signals adds raw per-shape signals; see Import signals)

The export path validates OPF with @openpresentation/opf, maps slide titles and common content payloads to editable PowerPoint objects through pptxgenjs, then normalizes the generated ZIP for stable entry ordering, fixed timestamps, and reproducible bytes.

import { toPptx } from "@openpresentation/opf-pptx";

const bytes = await toPptx({
  $schema: "https://openpresentation.org/schema/opf/v1",
  name: "Quarterly Review",
  slides: [
    {
      title: "Revenue grew across all regions",
      items: ["North America +18%", "EMEA +14%", "APAC +11%"]
    }
  ]
});

await fs.promises.writeFile("quarterly-review.pptx", bytes);

toPptx returns a Uint8Array containing a PowerPoint-openable .pptx. It does not fetch remote assets. Data URI images and local paths can be embedded directly; hosts that need private asset loading should pass imageResolver(src, context). Every picture path (image blocks and region images, quote photos, slide and deck background pictures, watermarks, cover logos, header and footer images and logos, picture bullets) reports an unresolved source with exactly one unresolved-asset diagnostic at its OPF path and draws the preview's fallback (the placeholder, the background colour, no watermark, or the character bullets), so strict CLI export (--fail-on warning) gates it. A local file path that cannot be read is unresolved like any other source (reason: 'file-unreadable'), instead of failing the export. Set strictAssets: true to turn unresolved or remote image assets into structured OPFPptxError failures instead of editable placeholder boxes. The "Image unavailable" placeholder (a dashed panel OPF image placeholder N with its label lines, or a cross when no label fits) is one native group OPF image placeholder N group (opf-pptx#221) that carries the accessible name (Image unavailable: <alt>) as its alt text, so PowerPoint moves or deletes it as one object while each member stays an editable shape; a logo and a header or footer image keep their ungrouped panel (generated furniture is never grouped). In full provenance the panel of an image block carries an OPF_IMAGE_PLACEHOLDER_V1 tag (the slide, the block path, the authored image value and the block's own fields), and fromPptx turns it back into that image block; without the tag (references-only, provenance: false) or with a damaged one (invalid-image-provenance) the shapes import as ordinary content.

Embedded picture bytes are a PNG, JPEG, GIF, WebP or SVG. Bytes that are no readable image (or an SVG that cannot be exported: malformed XML, no xmlns, no width and height and no viewBox, or no rasterizer, see below) export what the preview shows, like an unresolved asset: the "Image unavailable" placeholder (nothing for a watermark, the background colour for a background image) and one unresolved-asset diagnostic with a reason (unsupported-format, svg-malformed, svg-no-size, svg-too-large, svg-unsafe, svg-unreadable, svg-rasterizer-unavailable, svg-render-failed); strictAssets throws unsupported-image-dimensions for an unreadable raster, invalid-svg-image for an unusable SVG, svg-rasterizer-unavailable or svg-render-failed.

SVG pictures are native SVG pictures, as PowerPoint 2016 and Microsoft 365 store them: a p:pic whose a:blip embeds a PNG fallback raster and carries the asvg:svgBlip extension that points at the SVG media part (image/svg+xml, content type registered). PowerPoint 2016+ draws the SVG (crisp at any zoom; Graphics Format, Convert to Shape); older viewers draw the PNG. This covers image blocks (placed or not), the alt-text picture of an image background, the watermark, header and footer images and logos, and cover and section logos. The picture takes the same frame and fit (cover, contain, stretch) as a raster of the same proportions; the SVG's size is its width and height (any unit), else its viewBox. Exceptions, each a raster only: a picture bullet (a:buBlip can reference a raster only, so it is the PNG), an image background without alt text (a background picture fill carries no SVG), a tiled background picture, and an image block with a duotone recolor or a non-rectangular shape (the effect is written on the PNG blip, so it applies as in the preview, and svg-image-rasterized is reported). Opacity (a translucent watermark, slide image opacity), grayscale and the slide image border stay native SVG pictures: PowerPoint applies them to an SVG picture.

The PNG fallback is drawn at 192 dpi of the displayed size (a multiple of 128 px on its long side, 128 to 2304 px, transparent background) by options.svgRasterizer(svg, {width, height, scale, text}). In Node the default is opf-render's toPng (resvg with the bundled fonts and no system fonts, so the same SVG gives the same bytes on every machine), loaded only when an SVG is exported: it is the optional peer @openpresentation/opf-render. Without opf-render and without svgRasterizer (a browser build has no default; pass one that draws the SVG deterministically), the SVG picture is the placeholder with reason: "svg-rasterizer-unavailable". From opf-render 0.16 the PNG converter is itself an optional peer of opf-render: install @resvg/resvg-js next to it (npm install @resvg/resvg-js@^2.6.2; with text, also the font packages of the base pack, @expo-google-fonts/roboto and roboto-mono). When opf-render rejects with converter-missing the exporter reports the same svg-rasterizer-unavailable and the message carries opf-render's install command; with strictAssets the export throws that code. An imageResolver that returns a raster for an SVG source still gives a plain raster picture, and one that returns SVG bytes (mediaType: "image/svg+xml") gives a native SVG picture. A local .svg path is read in Node.

An SVG is never run or fetched. It is validated as XML and sanitized before it is embedded (and on import): <script>, <foreignObject>, event-handler attributes, link animations, every href/url() that is not a #fragment or an inline PNG, JPEG, GIF or WebP, @import rules, xml-stylesheet instructions and DOCTYPE declarations are removed (a DOCTYPE entity that is plain text is expanded; an external or nested entity is refused as svg-unsafe), and svg-sanitized reports what was removed. An SVG that needs none of that is embedded byte for byte, so import returns the authored source.

fromPptx reads a picture that carries asvg:svgBlip back as the SVG (a data:image/svg+xml;base64, source), not its PNG fallback, from this exporter's files and from PowerPoint's (a damaged or hostile SVG part imports its PNG fallback and reports invalid-svg-image, or imports sanitized with svg-sanitized). Design fields, organization logos and blocks with SVG sources (organization.logo, design.watermark, image blocks with their treatments) round-trip as SVG.

fromPptx parses an existing .pptx buffer locally and returns an OPF document that validates with @openpresentation/opf:

import { fromPptx, toPptx } from "@openpresentation/opf-pptx";

const opf = await fromPptx(await fs.promises.readFile("source.pptx"));
const roundTripBytes = await toPptx(opf);

await fs.promises.writeFile("round-trip.pptx", roundTripBytes);

The importer reads core properties, slide order, text boxes, speaker notes, embedded images, tables, and basic cached chart data from the OOXML parts. Slides or objects that do not map cleanly fall back to editable blocks[] payloads; OOXML positions are used for deterministic ordering and title/subtitle detection while keeping the emitted OPF schema-valid.

New in 0.10.0: native notes and property whitespace

Current source preserves spaces, tabs, NBSP and authored CR/LF/CRLF in speaker notes and scalar presentation name, description and author; published npm 0.9.1 does not contain this repair (0.10.0 does). Import reads current native notes body paragraphs in run/field/line-break order, retaining blank paragraphs. Explicit paragraph and line-break boundaries import as LF. XML character references decode once, so literal text such as &#13; remains literal. Export writes authored CR as character references in native text, without adding source-recovery tags; ordinary exports with no authored CR remain byte-identical.

Native edits, cleared/deleted note bodies or parts, core-property edits/deletions and slide relationship order remain authoritative in all provenance modes. Empty notes and absent notes still both import absent; an empty or absent title uses the existing fallback, and empty description/author import absent. Author arrays still export as a joined scalar. External literal XML line endings follow XML normalization; exact CR requires character references. These are portable XML conversion controls, not native Office or visual acceptance.

v1 Placeholder and OOXML Mapping

The first exporter keeps the public API stable while using pptxgenjs internally:

  • Slide.title, Slide.subtitle, and Slide.tag become editable text boxes, not PowerPoint master placeholders.
  • Root payloads, blocks[], and promoted region keys become editable slide objects in deterministic regions. Promoted keys use the OPF 3x3 region vocabulary (top, middle, bottom, left, center, right).
  • Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects. Content ColorRef values (hex, scheme slots/roles, and var:<id>) resolve through core resolveColorRef(). Slot and role names become native a:schemeClr references when the exported theme holds that exact color; hex, var:<id> and slide-override colors stay a:srgbClr (see Theme color scheme). Each classic chart type the core catalog keeps (one per Aspose.Slides ChartType: clustered/stacked/100% stacked column and bar, line and stacked line with or without markers, area/stacked/100% area, pie, doughnut, scatter with markers, radar/radar with markers/filled radar) is written as its exact native construct; deprecated core ids export as their replacement; other ids keep the previous best-effort mapping. The chartex types treemap, histogram, pareto, box & whisker, waterfall and funnel are written as native Office 2016 chartex parts (confirmed in desktop PowerPoint on 2026-09-30; see Chartex charts); the map (world) stays a clustered column chart by default and reports chart-data-adapted (chartex-fallback) until PowerPoint accepts its regionMap part (chartex: "native" writes it anyway, chartex: "fallback" writes clustered columns for every chartex id). A pie or doughnut chart, or a one-series chartex construct, plots only its first series and reports chart-data-adapted (series-dropped) when it has more. Every classic chart part follows the dml-chart.xsd child sequences (opf-pptx#175): each series is written in its CT_*Ser order (a line series has c:marker before c:dLbls), a radar series carries no c:invertIfNegative, and the scatter X c:valAx carries no category-axis elements.
  • A timeline event's status (done, current, planned) is drawn from the deck's colors with core's shared shapes (the same as the preview): a filled ellipse marker for done and for no status, a current marker inside a ring (a second ellipse, tagged with its marker) with a bold label, and a planned marker that is an ellipse with the background fill and an accent outline, with muted text kept at 4.5:1 or more. Import restores status from the marker tags, not from the shapes' look, and drops the timeline provenance (keeping plain text) when a current event's ring is missing or duplicated.
  • A chart whose data is one column of values (a histogram or dot plot) has no category column. A histogram or Pareto chart bins the values itself (PowerPoint's histogram with an explicit automatic bin count, no diagnostic) and re-imports as authored; any other chart type plots the values against their row numbers and reports chart-data-adapted (row-numbers). With chartex: "fallback" a histogram is binned into equal-width bins (Sturges' count, at most 50) and written as a column chart of the counts (histogram-binned; the binned counts do not round-trip). Cells parse as in every chart (core chartNumber: numbers and plain decimal strings; "12%", "$5" and "1,234" are not numbers) and cells that hold no number are skipped, not plotted as 0. Chart data that cannot be plotted (no numbers, no rows, or an external data source), an empty table and unsupported content keep a placeholder frame with a plain-language description (never a dump of the source data or URLs) and report chart-data-unplottable or content-placeholder with a reason; content is never dropped without a diagnostic.
  • Image assets are embedded only when supplied as data URIs, local paths, or host-resolved bytes/paths. Remote asset URLs are never fetched by the runtime path. A picture's native description is the authored alt (never a stand-in such as preencoded.png or the file path), and a distinct asset title is the picture's native title; fromPptx reads both back.
  • A chart cell that holds no number (null, an empty or non-numeric string, a boolean) is a gap in the native chart: its cache has no point at that row (c:ptCount keeps the row count), its workbook cell is blank, and it re-imports as null. An actual zero stays zero.
  • ZIP entries, generated chart/workbook part names, core-property timestamps, and nested chart workbook timestamps are normalized for reproducible bytes. Export determinism lists the tested time zone, locale, clock and host-font controls and each known variance (WebP conversion, font registries, ICU segmentation, timestamps).
  • The package names only the document's chosen fonts. Chart text (data labels, axes, legend, titles) uses the chart slide's body font in latin/ea/cs; each embedded chart workbook uses the same fonts in its styles and theme; run pitchFamily follows the font scheme type (monospace is fixed pitch, serif is roman); and docProps/app.xml "Fonts Used" lists the fonts the package actually uses. The theme keeps PptxGenJS's per-script supplements (THEME_SCRIPT_SUPPLEMENTS) and empty ea/cs slots unless a script font was selected for that slot (see Languages, right-to-left text and script fonts).
  • checkTypefaces(bytes, {families, monospace, themeScripts, themeScriptsByPart}) (optional themeScripts: {major: {ea, cs}, minor: {ea, cs}} names the East Asian / complex-script families the author selected; a selected theme slot that is empty or different is reported as theme-script-slot, an unselected one may stay empty; themeScriptsByPart overrides it per theme part, for the theme of a per-slide script master; theme references resolve against the theme of the part's own slide or notes master) inventories every typeface, workbook font name and "Fonts Used" entry in every XML part, including nested packages, and reports each font outside that policy. inventoryTypefaces() returns the raw inventory.

This pass did not require an OPF schema change. The deferred full OOXML placeholder mapping from docs/plans/layout-placeholders.md remains a later hand-written OOXML emitter concern.

New in 0.10.0: explicit ZIP dates

The following behavior is in current source; the published npm 0.9.1 package does not contain this repair or tightened option contract; 0.10.0 does.

Omitting zipDate (or passing undefined) keeps the established fixed 1980 ZIP bytes. Explicit zipDate values now encode UTC calendar fields in both the PPTX and embedded workbook ZIPs, independently of the host timezone. This option changes ZIP metadata; the separate timestamp option controls core-property XML.

Accepted values are a valid Date, finite epoch milliseconds, YYYY-MM-DD (UTC midnight), or YYYY-MM-DDTHH:mm[:ss[.fraction]] ending in Z or ±HH:mm. Calendar components must be valid and the resulting UTC year must be 1980–2099, matching the existing ZIP writer's supported range. ZIP timestamps have two-second resolution: fractional and odd seconds are truncated.

This intentionally tightens the previous host-dependent Date parsing contract. Datetimes without a timezone, legacy date strings, invalid dates, and values outside that range throw OPFPptxError with code invalid-zip-date and path options.zipDate. Explicit null, '' and 0 no longer silently use the default (0 is a 1970 epoch date). Existing successful UTC output and default output remain byte-identical; explicit dates on other timezones change to the canonical UTC result.

v1 Import Mapping

Quote provenance (FF-57)

A quote payload exports as native text lines: the body (the quoted text wrapped in straight quotation marks) and, when there is an attribution, role or source, one footer: the attribution, the role on its own line below it, and the source after - on the last line. Each line shape carries an OPF_QUOTE_V1 tag that stores only topology (whether the value was the string shorthand, which footer fields exist, where each separator falls, whether the quote has a photo, and how many source lines each part has). No quote word is stored: every value is read back from the current native text, so a cleared or edited quote cannot bring back the words it once held.

An unchanged export re-imports as { "type": "quote", "quote": ... } exactly: the string shorthand stays a string, text/attribution/source come back with their whitespace, hard line breaks and inner quotation marks, and several quotes on one slide keep their order. Native edits to the body or footer text import as the edited quote. The importer reports quote-import-reflow (native formatting, position and font theme are not reconstructed) and quote-footer-merged when an edited footer no longer separates attribution from source. A missing, duplicated or reordered line, a changed manifest or a native bullet on a tagged line rejects the group: the shapes import as ordinary text blocks with an invalid-quote-provenance diagnostic and no old words are restored. A quote authored without tags (for example in PowerPoint) imports as text. The output stays ordinary editable PowerPoint text boxes; the tags are p:custDataLst entries that PowerPoint keeps.

A quote with a photo (FA-12) also exports the headshot as one native picture in the circle frame core computes beside the footer lines: cropped to fill it (a:srcRect), with the ellipse preset geometry, the asset's alt text as the picture description, and the same OPF_QUOTE_V1 group tag (role photo, no words). Import restores role from the footer and photo (src, alt) from the current picture; a picture that was deleted imports the quote without a photo and reports quote-photo-missing, and a renamed or duplicated one rejects the group like a damaged text line. An SVG photo exports as its PNG raster (svg-image-rasterized), because the circular mask is not confirmed on an SVG picture in PowerPoint. An unresolved photo source draws the ordinary image placeholder.

New in 0.10.0: current native body formatting

Current source imports supported formatting from ordinary untagged native body text and list items. A value that previously imported as a string can now be a rich-run array containing the same current characters with explicit native properties. Unstyled values remain strings; title/subtitle selection and tagged recovery stay separate. Run, field and break order, blank paragraphs, significant whitespace, explicit normal overrides, paragraph/list defaults, point sizes, Latin font families, supported colors/alpha, hyperlinks and script direction come from the current PPTX. Edits, clears and deletion remain authoritative in every provenance mode. Native weight faces of the bundled Roboto family (for example Roboto SemiBold) import as Roboto plus bold for weights of 600 and above. Medium (500) and lighter weights import as regular, because OPF runs have no numeric weight, so a round trip re-exports Medium as Regular; approximate-body-font-weight reports this. Authored families such as Arial Black or Aptos Light keep their names.

This does not reconstruct original source run identities or boundaries between native shapes, infer master/layout text styles, or recover cached authored content. Native paragraph/break boundaries become LF; character-reference CR remains explicit. Unsupported properties report body-path diagnostics rather than claim exact formatting. Reading order still uses current native positions. Published npm 0.9.1 does not contain this representation change; portable conversion checks do not establish Office rendering or general rich-text round-trip fidelity.

The first importer is mechanical and schema-compatible:

  • Presentation core properties map to OPF name, description, and author.
  • The first slide master's theme clrScheme maps to design.colorScheme: the bare id of a colour scheme in the host default catalog (the first of fromPptx's catalogs) on an exact twelve-slot match, otherwise inline slots. A theme named like a theme of that catalog maps to design.theme when its colors or heading/body fonts corroborate it. With no catalog registered the colour scheme is inline and no theme id is recovered (see Theme color scheme).
  • Rich headline text (FA-10): a TextRun[] title, subtitle or tag exports as one tagged native text box per laid-out line with its runs (color, weight, italic, links, super/subscript, citation markers), and a TextRun[] quote.text the same way under the quote tags. Import returns TextRun[] where a heading's native runs are formatted differently from one another and a plain string where they are uniform: a tagged export records the heading's own weight, color, size and family (base), so only authored run formatting comes back, and a native title placeholder is read like body text, dropping what every run shares. A weight that the export writes as bold on every run of a title (and on a quote body) is the default, so only a bold: false word in a title is kept; bold: true in a quote body is indistinguishable from the body weight and is not restored. Marker runs become cite/footnote again, and heading and quote runs take part in the run-color reference slots.
  • Native title/subtitle placeholders retain their roles. On slides without complete OPF heading tags, recognizable text-box positions and sizes provide a fallback. If any complete OPF heading role is recovered, untagged body text stays in blocks[] instead of being promoted into an absent heading role. Damaged tags retain visible text through ordinary import and diagnostics.
  • Remaining text boxes map to blocks[] as text or list payloads, sorted by OOXML position. A measured list writes its size, family, weight and colour on every native run; in full provenance the slide record (OPF_SLIDE_V1.listRuns, opf-pptx#212) keeps that look per list (entry and description lines, never text), and import removes a run value still equal to it, so an untouched list round trip returns its items as authored instead of baking those values into TextRuns. A value edited in PowerPoint differs and comes back as observed; a run colour reference that resolves to the list colour (the text role) still returns through the run colour record, but an authored literal colour, size or family equal to the list's own cannot be told from it and returns absent. Without the record (references-only, provenance: false, a plain PPTX) runs are read as observed.
  • PowerPoint tables map to OPF table blocks, embedded images map to data URI image blocks, and cached chart series map to OPF chart blocks whose type is the core chart type id for the native construct (for example a percentStacked column chart imports as 100pct-stacked-column, a filled radar as filled-radar; a scatter chart imports as [Point, X, ...series]).
  • Chart cache points are placed by their native c:pt@idx, with c:ptCount retaining trailing gaps. Missing labels and values become null; explicitly empty labels and series names stay empty strings, and an actual numeric zero stays zero. Empty numeric cache values remain missing. Malformed or duplicate indices, contradictory counts, competing caches and allocation limits reject with invalid-chart-cache and the chart-part/cache path; hierarchical category caches reject with unsupported-chart-cache instead of flattening labels. Each cache is limited to 100,000 positions, and combined caches and the emitted table each to 1,000,000 cells. Complete decimal scientific notation in numeric caches is read as a number; exponent overflow or nonzero underflow to zero rejects with unsupported-chart-cache and the chart part, series, cache and logical point path. Zero coefficients remain zero. This finite-number boundary does not classify rejected text as invalid OOXML. Malformed exponent strings and other nonblank numeric text retain the existing legacy conversion policy. This cache-only import does not repair embedded worksheets, restore unsupported scatter X/point labels, change exporter/workbook parsing or export/re-export missing-data handling, or establish native chart fidelity.
  • Table imports retain empty rows. A native firstRow flag of 1 or true maps the first row to column labels; absent/false flags retain every row as data. New exports set this flag from OPF columns. Older exports without the flag retain their labels as the first data row rather than inferring headers.
  • Native table text preserves run/field/break order, significant whitespace, and blank paragraphs. Cells return canonical {value, style} objects; value retains scalar text or supported rich runs. Covered merge positions are null. Explicit normal headers override OPF’s bold header default. Numeric/boolean/null source types cannot be reconstructed from native display text.
  • Run lang maps back to the presentation language as a BCP-47 tag (the run's own tag, for example ja-JP); see Languages, right-to-left text and script fonts.
  • Imported runs retain bold, italic, underline, strike, point sizes, Latin font families, solid colors/alpha, external hyperlink URLs, and superscript/subscript direction. List-level and paragraph defaults apply before run overrides; supported theme fonts/colors resolve from the archive. Field values become their cached text, and underline/strike variants and baseline magnitudes reduce to OPF booleans.
  • Citations, footnotes and captions (core RR-34 fields): a marker exports as a native superscript run (baseline="30000") after its run; the slide's footnote area is a line shape plus one tagged text box per listed line (OPF_FOOTNOTES_V1), a caption one tagged text box per line (OPF_CAPTION_V1) naming its media shape, both at core's geometry; references travel in the document record. Import re-attaches tagged captions to their media block, rebuilds references, cite and footnote from the footnote tags and the marker runs (removing the marker runs; an edited note keeps its edited text), and guesses nothing without tags: a superscript number stays a superscript run and a text box stays a text block.
  • Conditional table styles, merged-cell geometry, cell fills/borders/alignment, unsupported text fills/colors, and internal hyperlink actions are not fully reconstructed. onDiagnostic reports unsupported table style references, merges, fonts/colors/fills and links with native frame/cell paths. Table paths use the native graphic-frame and row indexes, including a header row. The shared core 0.6.0 layout sizes rows from their content and reports text-overflow when text cannot fit at the minimum size. These checks establish native XML conversion, not visual parity with PowerPoint.
  • Unknown non-text shapes and unsupported graphic frames become editable text fallback blocks instead of failing the import.
  • Catalog references (design.theme, colorScheme, fontScheme, dimensions, background, slide layout), slide ids and authoring metadata (narrative, tone, audience, purpose, language, organization, speaker, ...) are stored at export in OPF_DOCUMENT_V1 / OPF_SLIDE_V1 customer-data tags. Import restores a reference while the theme colors, theme fonts, slide size, background or slide arrangement it produced are unchanged. After an edit, the observed native values stay and design-reference-changed / layout-reference-changed name the reference. toPptx option provenance: 'references-only' | false limits or disables these invisible tags. In full mode the document tag also lists the document-level defaults the document left to the engine ($schema, name, language, the theme, colour scheme, font scheme and slide size), and import leaves them absent while the package still shows the default, so an untouched round trip is deep-equal to the source (RR-59); a value edited in PowerPoint imports as observed. Slide layout intent (layout reference, type, composition, composition hints and the embedded layout record with its catalogs group and source) lives in each OPF_SLIDE_V1 record, so a slide keeps its layout even without the document tag, for example when it is pasted into another deck (FF-29). A layout that resolves only in a registered catalog stores no record and restores where fromPptx is given that catalog. See document round trips for exactly what is embedded.
  • The slide's content structure is part of OPF_SLIDE_V1 too (full mode): nested group blocks, promoted regions (left, top:left, ...), a root payload (text, items, chart, ...) versus blocks, block ids and extensions, group composition, with the reference-pixel box of every leaf from the same composition the export drew. Import matches the native shapes to those boxes and rebuilds the authored form while the slide's arrangement is unchanged, so slide.type validates again; a slide whose blocks no longer fit the stored boxes keeps its flat blocks and reports content-structure-changed at slides.N; a duplicated slide reports duplicate-block-id and drops the repeated id. The document tag also stores filename, root extensions, slide section and extensions, design.logo (deck and slide: a logo reference or false), the organizations with their logos and the whole assets registry; a data: source that is not an exported picture is stored inline up to 256 KiB, so an organization logo or speaker photo given as a data URI round-trips.
  • Slide section labels are written as PowerPoint's native section list (p14:sectionLst in ppt/presentation.xml, whatever the provenance option): one section per run of consecutive slides with the same label, runs without a label as Default Section, with deterministic ids. Import reads the list back (Default Section means no section); it wins over the stored value (a footer's {{slide.section}} is only the token, so no footer text names the section), except that the stored label stands while the list still names it. The native PowerPoint check of the sections pane and save/reopen is a separate gate (scratchpad/spec-gaps-native/).

There is no AI classification pass in the OSS runtime. Hosts can run optional cleanup or semantic remapping after fromPptx returns. A host that wants to rebuild structure (code, metrics, quotes, timelines, regions) in a deck that did not come from OPF can ask for the raw shape facts such a step needs; see Import signals.

Import signals

A deck exported by this package carries OPF provenance tags, so its structure returns exactly. A deck from anywhere else does not: a code panel is a text box with a monospace font, a metric is a large number over a small label, a quote is italic text over an attribution, and fromPptx imports each as plain text, because telling them apart takes judgement. That judgement is not made here. fromPptx only reports what is in the package, so a host can run its own classifier (a model, a rules engine, a person) over it. The runtime policy is unchanged: no network, no models, no clock, no randomness, nothing fabricated.

import { fromPptx } from "@openpresentation/opf-pptx";

// Default: the presentation.
const presentation = await fromPptx(bytes);

// Opt in: the same presentation, and a signals value beside it.
const { presentation: same, signals } = await fromPptx(bytes, { signals: true });
// or with limits: fromPptx(bytes, { signals: { maxSlides: 100, maxShapesPerSlide: 150 } })

Without signals (or with false) the return value and the document are byte for byte what they were; with it the presentation is identical and the result is {presentation, signals}. Invalid limits throw OPFPptxError with code invalid-signals-option before any work. signals is plain JSON (JSON.stringify and back loses nothing), the same bytes in always give the same signals, and the types are PptxSignals in index.d.ts.

What it reports. signals.deck has the slide size (EMU and reference px, 96 per inch), the theme (colour and font scheme names, major and minor font, the twelve colour slots) and whether the deck carries OPF tags. signals.slides[i] has the part, layout (name, type, part), master (part, name, theme name), whether the slide is hidden, whether it carries an OPF slide tag, and shapes: every shape in z-order (document order, back to front; zOrder 0 is the back), each with:

  • kind: text, shape (an autoshape with no text), placeholder (an empty placeholder), picture, media (video or audio), table, chart, smartart, ole, group, connector, line or unknown (with graphicType). placeholder carries type, idx and size.
  • box: px (reference px) and emu, in slide coordinates with group transforms (offset and scale) applied, so a shape inside a scaled group has its real position. A placeholder with no box of its own gets the layout's, then the master's (box.source). rotation, flipH and flipV describe the shape; its box is the frame before rotation, and box.approximate marks a box inside a rotated or flipped group.
  • parent, children and depth give the group nesting; id is s plus the z-order index.
  • text: paragraphs with level, align, the effective bullet (inherited from the list style when the paragraph states none), indents and spacing, and runs with font, size, bold, italic, weight, underline, strike, baseline, caps, color (#RRGGBB) with its theme colorRef, monospace, link, field and lineBreak. Values are the effective ones PowerPoint draws: the run, its paragraph, the shape's list style, the layout and master placeholder, the master text styles, the presentation defaults and the theme (font tokens such as +mj-lt and scheme colours are resolved). A value no part of that chain states is omitted, and defaults are omitted (bold, italic, monospace absent means false, weight absent 400, level absent 0, align absent left). monospace is true for a known fixed-width family (Consolas, Courier New, Menlo, JetBrains Mono, ...); text.monospaceShare, dominantFont, dominantSize, maxFontSize, anchor and autofit summarise the shape.
  • fill, outline (kind, colour, width, dash, arrowheads) and geometry (rect, roundRect, ellipse, ...). Fills and lines that only name a theme style are reported as kind: "style" with the style index and colour.
  • picture (part, media type, bytes, natural size, crop), table (rows, columns, widths, header flags, cell text), chart (chart types, series and point counts, title).
  • opf: where the importer put the shape in the document it returned. {role: "block", path: "slides.2.blocks.1", blockType: "text"}, {role: "title", path: "slides.0.title"}, or null when the importer made nothing of it (a connector, a table inside a group). For an OPF export the path follows the rebuilt structure (slides.0.blocks.0.blocks.1, slides.1.left, slides.2.text). A shape the importer consumed without a block of its own has a role only (furniture, logo, watermark, background, image-overlay, or the member of a tagged group). A group links where all its members do. Several shapes can link to one block (the lines of a list, the parts of a quote).

Bounds. The output is bounded by signals limits, reported in signals.limits (defaults, with the largest value each accepts): maxSlides 300 (5000), maxShapesPerSlide 300 (5000), maxTotalShapes 5000 (100000), maxParagraphsPerShape 200 (5000), maxTextCharsPerShape 8000 (200000), maxTextCharsTotal 400000 (5000000), maxTableCells 400 (10000), maxTableCellChars 200 (5000), maxGroupDepth 12 (32). What a bound drops is counted: signals.truncated (slides not reported, text budget exhausted), slide.truncated (shapes, depth, paragraphs, text, table) and paragraph.truncated. text.chars and text.paragraphCount are the true counts. Images are never embedded, only described, so the size follows the text: about 0.7 KB of JSON per shape.

Not claims. Signals are facts, not conclusions: a monospace run is not a code block, and opf: null is not an error. They are read from the package, not rendered: the effective values follow the OOXML inheritance rules, not PowerPoint's layout engine (text wrapping and autofit are not computed, a group's rotation is not applied to its members' boxes, and an unresolvable colour or font is left out). They describe the upload, which can be hostile: the text is the deck's text, and a host that passes it on to a model should treat it as untrusted data, never as instructions.

Combo charts (FA-15)

type: "combo" is exported as one classic chart part: one c:plotArea with a clustered column c:barChart on the primary axes and a c:lineChart (markers, c:grouping standard) for the line series. Lines named in chart.secondaryAxis form a second c:lineChart on a second c:valAx (axPos r, crosses max, its first series' number format, no gridlines, axisTitles.secondary as its c:title) and a deleted second c:catAx. Core's resolveChartData orders the series (columns, then primary-axis lines, then secondary-axis lines), each keeps its palette colour by its place in the whole chart, the embedded workbook holds every series, and a gap breaks a line (c:dispBlanksAs gap) as in the preview. The line groups are rewritten into dml-chart.xsd order (c:grouping first, no c:invertIfNegative, c:marker before c:dLbls). Import reads a plot area with one clustered column group and one or two line groups as combo, with line (only when it is not the default last series), secondaryAxis, the secondary axis title and the data label position; the authored column order comes back through the chart's data record.

Chartex charts (FF-22b)

Modes. toPptx(document, {chartex}) takes "auto" (default), "native" or "fallback". The native check of 2026-09-30 (desktop PowerPoint, read-only, the program's FF-22b deck set) opened every chartex deck without a repair prompt and reported the native Chart.ChartType for treemap (117), histogram (118, by value and by category), pareto (122), box-and-whisker (121), waterfall (119) and funnel (123); for world PowerPoint took the mc:Fallback (51, clustered column) when the choice required cx5. The recheck of the same day showed that PowerPoint accepts the regionMap part when the choice requires cx4 (the 2016/5/10 region-map namespace; cx3, cx4 and cx6 reported ChartType 140, cx5 and cx8 fell back) but, with no cx:geoCache, shows "There was a problem getting the information for your map chart" and draws nothing until it can fetch map data online. Supervisor decision: "auto" writes the chartex parts for the six confirmed constructs and keeps world on the clustered column chart, which always renders, with chart-data-adapted (chartex-fallback); "native" writes every chartex part, the map included with Requires="cx4" and the chart-map-geodata diagnostic (provider data is never fabricated); "fallback" writes clustered columns only, byte for byte as before FF-22b (test/fixtures/chartex-fallback-main.json pins it, with chartex-fallback and histogram-binned). The same check showed chartex labels drawn in the chart style's theme grey and box lines in black on a dark theme, so every chartex text element and the style part now carry the deck's label colour and font explicitly, and box series carry a line in the label colour. Import of chartex parts is always on.

The seven kept chart types with no ECMA-376 construct are written as Office 2016 chartex parts (ppt/charts/chartExN.xml, cx:chartSpace, content type application/vnd.ms-office.chartex+xml), each with its chart style and colour style parts (styleN.xml, colorsN.xml) and the embedded workbook the data came from. PptxGenJS cannot write these parts, so the exporter first writes the classic clustered column chart of the same data and then post-processes the package (src/chartex.js): the slide's chart frame becomes an mc:AlternateContent whose mc:Choice (requiring the construct's chartex namespace) references the chartex part and whose mc:Fallback keeps the classic chart frame, so a reader without chartex support still shows the data as columns. Both parts share the workbook. cx:series uniqueId values are derived from the chart number, so the bytes stay deterministic.

OPF id cx:series layoutId Data (category-major) Notes
treemap treemap [Category, Value], first series one tile per category, category data labels, one palette colour per tile
histogram clusteredColumn [Value] or [Category, Value], first series a lone value column is binned by PowerPoint with an explicit automatic bin count (Scott's rule, cx:binCount); a category column bins by category (cx:aggregation)
pareto clusteredColumn owning a paretoLine as histogram the Office Pareto chart: sorted columns plus the cumulative-percentage line on a percentage axis
box-and-whisker boxWhisker [Category, S1, S2, ...], every series rows with the same category form one box per series; exclusive quartiles, mean markers, outliers
waterfall waterfall [Category, Value], first series connector lines; increases and decreases in the first two palette colours; no subtotals
funnel funnel [Category, Value], first series value data labels
world regionMap (chartex: "native" only; Requires="cx4") [Region, Value], first series no cx:geoCache: PowerPoint accepts the part (ChartType 140) and must fetch the region shapes from its online map service; until it does it shows "There was a problem getting the information for your map chart" and draws nothing; reported as chart-map-geodata. By default ("auto") the map stays a clustered column chart

The chart area, label colour, label font (latin/ea/cs in the chart's body font) and series colours follow the classic charts. Re-import (fromPptx) reads the chartex part first: the layoutIds name the OPF id (an owned paretoLine is pareto), cx:strDim type="cat" restores the categories and each cx:numDim a series, under the same 100,000-point and 1,000,000-cell bounds as classic chart caches; a lone value column comes back as authored. A chartex part with no OPF construct (sunburst) falls back to the mc:Fallback chart, or to a text placeholder when PowerPoint's own text fallback is all there is. Native PowerPoint rendering of each construct is confirmed through the program's bounded native sample, not by this package's tests.

Pass fonts, otherwise the estimate

Text measurement comes from one place: the fonts handle that opf-render's loadFonts() returns (@openpresentation/opf-render/fonts-node, or /fonts-browser). Pass the same handle to preview, validation, pagination and toPptx, so that the exported text boxes break lines where the preview does:

import { loadFonts } from '@openpresentation/opf-render/fonts-node';
import { toSvg } from '@openpresentation/opf-render';
import { validate } from '@openpresentation/opf';
import { paginate } from '@openpresentation/opf/pagination';
import { toPptx } from '@openpresentation/opf-pptx';

const fonts = await loadFonts({ pack: 'office' });
const report = validate(deck, { fonts });
const { presentation } = paginate(deck, { fonts });
const svgs = toSvg(presentation, { fonts });            // preview
const pptx = await toPptx(presentation, { fonts });     // export: the same lines, as editable text

One handle carries the measurement for core's validate, paginate and slide context, for opf-render's toSvg, toPng and toPdf, and for toPptx. The same handle gives identical text geometry across engines: every engine reads core's one composition, so it is the measurement that decides the lines. This package's test/layout-parity.mjs checks that the exported PowerPoint paragraphs agree with the SVG preview and with core's composed items, with and without fonts.

Without fonts, toPptx uses core's built-in estimate: 0.54 em per character, 0.62 em for capitals and digits, 0.32 em for a space, 1 em for CJK characters and zero for combining marks. That is deterministic and needs no font files and no renderer, but it is too narrow for some scripts (opf#566 tracks improving it), and the line breaks it chooses can differ from the faces PowerPoint draws. The PPTX still names the font family the document chose (fonts changes where lines break, not which font is named). Core's Node convert and the opf CLI prepare an office-pack handle for you; a library call to toPptx never loads fonts on its own, so pass the handle you want. See also opf#364 and core's measured fonts.

Languages, right-to-left text and script fonts

The exporter reads the presentation language through core resolveScriptFonts() (FF-07; the model is core docs/programs/font-fidelity-everywhere/script-font-model.md):

  • Every run, end-of-paragraph and default run property carries lang set to the resolved OOXML tag instead of a fixed en-US. That tag is core's curated ooxmlLang for the language (for example ja-JP for ja or ar-SA for ar) or an authored region tag such as en-NZ. altLang is not written: it names the editing-UI language, which OPF does not model.
  • Theme major/minor a:ea/a:cs are written only for a slot where a script font is actually selected (FF-49): an explicit eastAsian/complexScript on the design font scheme, the scheme's own script family, or the language's script font (a language that uses the East Asian slot, such as Japanese, writes ea; one that uses the complex-script slot, such as Arabic, Hebrew, Indic or Thai, writes cs). Every other slot keeps the vendored empty typeface, exactly as Office's own themes leave it, so PowerPoint picks its per-language default for script text typed later and the package names nothing the author did not select (owner font policy). Latin, Cyrillic, Greek, Armenian, Georgian and Ethiopic decks write neither; the other slot of a CJK or complex-script deck is empty too. The language never changes the theme's latin fonts (Model C): design.fontScheme alone sets them. The preview resolves the same slots (opf-render's script profile: the theme's family where it names one, the latin family where it is empty), and test/theme-script-slots.mjs checks preview against export for every catalog font scheme.
  • The theme's per-script entry for the language's own script (for example Jpan, Hang, Arab or Deva) names the resolver's supplement. The rest of the vendored Office per-script list is unchanged; that list is FF-08's call.
  • Slide and notes runs name no a:ea/a:cs (FF-05: PowerPoint lists an explicit run East Asian or complex-script typeface as an empty-name font in Presentation.Fonts); their script text takes the theme slots through +mn-ea/+mn-cs. The theme ea always names a font (the selected script font, an East Asian font for East Asian text in the deck, else the latin family), so no paragraph end mark reads an empty font. Chart text keeps its own latin/ea/cs in the chart's body font.
  • Measured export uses the optional renderer's public script planner for each resolved slide and each run language. The same loaded fonts handle can measure Arabic/Latin text in SVG and PPTX; native font names still follow the document's selected scheme. Custom measurement providers remain active when the optional renderer is absent. These controls establish shared line geometry and editable source recovery; native Office appearance is separate.
  • Per-slide script fonts (opf-pptx#168): the presentation theme is built from the first slide. A slide whose own font scheme, slide theme or inline eastAsian/complexScript selects script fonts that theme does not carry gets its own slide master: a copy of slide master 1 and its layout, with a theme that is the presentation theme plus that slide's East Asian / complex-script slots and per-script entry (named 1_<theme>, 2_<theme>, ... as PowerPoint names a second master's theme). Slides with the same script fonts share a master, a slide that selects none stays on master 1, and masters are numbered in slide order. A deck with one script profile keeps one master and is byte-identical to the export before this change. A presentation has one notes master, whose theme is a copy of the presentation theme, so the notes of a slide on another master draw in the presentation theme's script fonts: script-font-notes-not-exported (path slides.N.notes) reports each such slide with notes.
  • In a right-to-left deck, each slide and notes paragraph takes its direction from core paragraphDirection(text, direction), the same rule the renderer uses: rtl="1" when its first strong character is right-to-left or it has none (digits, punctuation, empty), else an explicit rtl="0" (an English quote, a code line). The master, layout and presentation default paragraph levels start right-to-left only in a right-to-left deck. Left-to-right decks write no paragraph direction. Alignment is logical (RR-05, needs the core release that composes right-to-left decks): left is the start edge, so a right-to-left paragraph writes rtl="1" with algn="r" (PowerPoint's algn is physical, and marL/indent are the start-side margin and hanging indent, so a bullet hangs at the right), and every wrapped line shares its paragraph's direction and alignment (core computes the direction once per paragraph, not per line). The composition is mirrored through core: the left region and the first column are drawn at the right, a right-to-left table writes a:tblPr rtl="1" with its columns in logical order, column, line and area charts write c:catAx orientation maxMin (first category and value axis at the right), cover logos and header/footer zones swap sides, the master, layout and notes default levels start right-aligned, and notes paragraphs start at the right. Each Latin phrase inside an Arabic or Hebrew run (letters, joining spaces and word punctuation, digits) is written as its own en-US run, so PowerPoint orders v2.0 and PowerPoint 365 left to right instead of splitting them into items; digits that touch Arabic words stay in the Arabic run. Re-import reads right alignment of a right-to-left table cell as the logical start. Left-to-right decks are written exactly as before.
  • The PPTX names the chosen fonts only and never embeds font programs. Licensed fonts are never embedded; only open fonts could be, through the explicit FF-13 embed path. Catalog names such as Meiryo must be installed where the deck is opened.

Charts keep their c:lang and left-to-right label paragraphs. The embedded chart workbook is untouched.

When the package carries an FF-32 stored language (document round trip), that reference is restored while the runs still carry its OOXML tag (or none); if the runs now use another tag, the observed language below is kept and metadata-reference-changed is reported once. Otherwise fromPptx sets language to the most common run lang, as that BCP-47 tag (OPF 0.15: language is an engine vocabulary, not a catalog): a deck authored as ja exports ja-JP runs and, without provenance, imports as ja-JP; en-NZ imports as en-NZ. Diagnostics:

  • mixed-run-languages: runs use several tags; the most common one is imported.
  • rtl-language-mismatch: right-to-left paragraphs under a left-to-right language.
  • script-font-not-imported: theme ea/cs name a font that neither repeats latin nor matches what the imported document resolves for the first slide (path design.fontScheme), or a slide on another slide master has a master theme whose ea/cs the imported slide does not resolve to (path slides.N.design.fontScheme; with FF-32 provenance the slide's font scheme is restored and nothing is reported). Without provenance, imported OPF does not carry the script slots.

Exports report language-unresolved when core cannot resolve the document's language tag (a tag whose script it does not know) and en-US is used.

Small conveniences (FA-13)

  • code.highlight exports one native rectangle per run of marked lines (OPF code N highlight M) between the code panel and the one-text-box-per-line shapes; marked lines keep the syntax colours on the band and the others are dimmed, both at least 4.5:1 (core codeHighlightColors). The bands carry code provenance tags, so they never import as text and a band deleted in PowerPoint loses nothing; import restores highlight from the code tag.
  • Text watermark (design.watermark = { text, opacity }, or a slide's own): one native text box per slide named OPF watermark text, first in the shape tree, in the heading font, bold, the theme text color with the opacity as text alpha, centered and rotated 30 degrees counterclockwise (a:xfrm rot="19800000"), sized by core layoutWatermark. It carries an OPF_WATERMARK_V1 tag with kind: "text"; import restores design.watermark from an unchanged box and reports invalid-watermark-provenance for an edited one (the text then imports as ordinary text).
  • TextRun.code writes the design's code family (the font scheme's code role, else Roboto Mono) on the run. In a package this exporter wrote, import reads a run in the design's code family back as code: true.
  • TextRun.lang writes a:rPr lang (core's curated OOXML tag for the language, such as fr-FR for fr) with altLang="en-US", plus the run's own East Asian and complex-script faces for that language (Meiryo for ja-JP, Microsoft YaHei for zh-CN) where the language selects one. A run in the deck language writes no override. Import reads a run lang that differs from the deck language back into body text runs.
  • Presets 1:1, 4:5, 9:16 export as p:sldSz 6858000 x 6858000, 6858000 x 8572500 and 6858000 x 12192000 EMU. The exporter's document provenance restores the authored preset on import; a package without it imports the exact size as inches, which resolve to the same canvas.

Numbered lists (RR-33)

A list with numbering ({ "items": ["Define", "Build"], "numbering": ["arabic", { "style": "alpha-lower", "suffix": "paren" }] }) exports as native PowerPoint auto-numbers, not typed digits. Each marker line is the text box it already was, now with a:buAutoNum (arabicPeriod, arabicParenR, arabicParenBoth, romanUc/romanLc..., alphaUc/alphaLc...) whose startAt is the number core counted (written only when it is not 1), the measured marker size, colour and family (a:buClr, a:buSzPts, a:buFont) and marL/indent from core's hanging indent, so the number and the text sit where the preview draws them. A numbered list is not renumbered by PowerPoint when an entry is inserted (each line is its own box; this is how lists export today), the numbers are core's. fromPptx maps a:buAutoNum back: the scheme to a style and suffix, startAt to start, per level to an array, and a list whose numbers are not the plain count to per-entry start values; native counting follows PowerPoint (consecutive paragraphs of one scheme and start count up, a shallower or unnumbered paragraph ends the deeper sequences). The authored spelling comes back in its shortest form ("arabic" for { "style": "arabic" }). A scheme OPF has no equivalent for imports as arabic with numbering-scheme-adapted, a list that mixes numbered and bullet paragraphs imports as bullets with numbering-mixed, and different schemes at one level report numbering-style-adapted. A numbered list exported with a core that does not compose numbering reports numbering-unsupported-core and exports bullets. Lists without numbering export unchanged. See core's numbered lists.

Templates and variables (RR-32)

A deck that declares content variables, or is marked "template": true, is resolved by core resolveVariables before it is exported, so the PPTX holds exactly the text, numbers, dates and images the preview shows. Pass the values as variables:

const pptx = await toPptx(template, { variables: { client: 'Globex', revenue: 1250000 } });

A template exports with each unfilled variable's example and reports variable-example-used through onDiagnostic (a built-in variable such as {{speaker.name}} is read from the document's own speaker, organization and name; one with no source value exports as nothing and reports variable-builtin-missing); a normal deck with an unfilled required variable throws OPFPptxError with code unfilled-variables, and a value of the wrong kind throws invalid-variables. A template plus values exports the same bytes as the equivalent hand-written deck (test/template-variables.mjs). The package stores the resolved deck, not the template form: fromPptx returns the filled deck. Decks without content variables are untouched. See templates and variables.

Runtime Policy

The package runtime must stay local and deterministic:

  • No hosted service in the critical path
  • No telemetry or hidden analytics
  • No commercial SDK dependency in the critical path
  • No required network calls
  • No required AI dependency
  • No required AI cleanup or classification pass for PPTX import
  • No required LibreOffice dependency in the runtime path; LibreOffice is allowed only as an optional verification tool in CI
  • Host applications own auth, storage, queues, analytics, collaboration, branding, and product workflow

Development

npm ci
npm run build
npm run typecheck
npm test
npm run validate

LibreOffice is not a runtime dependency. When it is installed in CI or a local verification environment, generated .pptx files can be smoke-opened there as an optional export check.

Release Lane

Public npm package publication is handled by .github/workflows/release.yml through npm Trusted Publishing (GitHub Actions OIDC) with npm provenance; no npm token is stored. The owner authorized agents to prepare and publish npm releases whenever a release is required (2026-09-29). This authorization does not waive any gate.

  1. Open a release-prep PR containing only the version bump, CHANGELOG.md (assembled from changes/ with node scripts/changelog-fragments.mjs assemble --version X.Y.Z), dependency ranges, lockfile and current-instruction docs. Publish in dependency order (core, then renderer, then PPTX, then editor): refresh this repo's lockfile only after the required @openpresentation/opf and @openpresentation/opf-render versions are on the registry (npm install --package-lock-only), then run npm run test:packed against them.
  2. Merge after CI is green, then publish by pushing the git tag opf-pptx-v<version> (or @openpresentation/opf-pptx@v<version>) at the merge commit. The workflow verifies that the tag matches package.json and reruns audit, typecheck, validate, tests, packed and browser checks before npm publish --access public --provenance. A manual workflow_dispatch runs the same job without the tag check and is a fallback only.
  3. Verify with npm view @openpresentation/opf-pptx@<version> version gitHead dist.attestations and, from the core repo, node scripts/test-pptx-publication.mjs <version> <release-commit> <this-checkout>. Never republish an existing version.

Shared dynamic composition

The current checkout uses @openpresentation/opf/composition for portable geometry. Slides can select auto, row, column, or grid, set weighted tracks, and request path-specific overflow diagnostics. See the sibling OPF repo's docs/dynamic-composition.md for the complete contract.

Version 0.4.0 requires published @openpresentation/opf@^0.6.0. The optional renderer peer requires @openpresentation/opf-render@^0.4.0. Clean registry installs support the new composition APIs without sibling checkouts. For coordinated source development, build OPF and run node scripts/link-ecosystem.mjs there; pnpm test:ecosystem verifies shared geometry and import/export behavior.

For crowded drafts, run paginate(presentation, { fonts }) from @openpresentation/opf first, then pass its returned presentation to both preview and toPptx. Native table row sizing now follows shared reference geometry; the exporter does not add hidden table continuation slides.

Pass the same fonts handle used by preview and pagination to toPptx. Plain text and headings retain the measured line breaks in editable PowerPoint shapes.

The PPTX always names the font family the document chose (for example typeface="Aptos"), so PowerPoint opens the file and shows the actual font, installed or as a Microsoft 365 cloud font. This is the OPF font policy: the user's selection is the source of truth, licensed fonts are never bundled or embedded, and previews draw an open look-alike instead. A provider may preview that family with another face: a metric-compatible substitute (Carlito for Calibri, the goal), a visual-only one (Roboto or Carlito for Aptos, a documented fallback and known layout-fidelity gap until a metric-compatible replacement exists), a caller alias or a generic fallback. That face changes measurement and drawing only. It never reaches the theme, runs, bullets or chart parts. Faces of the chosen family itself, such as Roboto Medium for Roboto at weight 500, keep their native style-link names. test/export-chosen-fonts.mjs checks this for every renderer Office-pack substitute. The exporter does not embed font binaries. Viewers resolve the named family themselves. When the preview used a non-metric substitute, PowerPoint can break lines differently from the preview.

The exact pptxgenjs-plus 4.3.4 ESM distribution (the maintained PptxGenJS fork; PptxGenJS 4.0.1 from 0.5.1 to 0.12.x) is shipped with its MIT license and verified upstream hashes. Its imports (@node-projects/jszip, @rgrove/parse-xml, pako) are declared directly; image-size is not installed. Output changes of the move: docs/pptxgenjs-plus-migration.md. See dependency provenance and regression coverage. The published 0.5.0 package retains the older dependency graph.

Native table fitting

Version 0.4.0 imports and exports supported rich table cells and headers as editable runs, retaining resolved fonts, emphasis, color/alpha, hyperlinks, script positions and explicit line breaks. Import reads native XML, including paragraph defaults and significant whitespace; unstyled body cells remain strings. Shared core 0.6.0 layout grows rows for multiline content and fits text consistently with renderer 0.4.0 without inserting measured soft wraps. Native PowerPoint rendering remains unverified.

The exporter measures every cell with the same fonts handle, font roles and effective nested minFontSize used by the SVG preview. Native table cells retain the original strings and values as text, with matching fitted sizes, line spacing, alignment, margins and row/column geometry. Uneven rows receive empty cells for missing columns. Theme border colors now use the same slot as the preview.

npm test compares exported OOXML against the published SVG renderer across 168 cells, including 24 cases that require taller rows, Roboto, and Calibri measured with its metric-compatible Carlito substitute (the cells still name Calibri), two canvas sizes, headers and all three alignments. PowerPoint still performs its own natural wrapping and needs the named fonts installed. These document-property checks do not establish native raster parity or lossless typed-cell import.

A local macOS Quick Look check opened both Roboto and system-Arial specimens. Quick Look substituted a serif font for uninstalled Roboto; the Arial specimen used a sans-serif face but still differed in table wrapping and row proportions. This is evidence of remaining viewer differences, not a passing PowerPoint raster comparison.

Image geometry

Every content picture is an image block (OPF 0.15; Slide.image is shorthand for one). Core composes it as item.image (@openpresentation/opf/composition): the picture frame, the fit (the block's fit, else the effective design.imageFit, else cover), the focus point and the treatment. The export crops, pads and focuses the embedded bytes with core's fitImage, so the preview and the export show the same part of the picture: cover fills the frame with a native crop (a:srcRect) that keeps the focus point in view, contain shows the whole picture centered, and stretch fills the frame exactly. A plain contained picture in the content flow is framed by the picture itself; a placed or treated one keeps core's frame and pads with negative a:srcRect insets, so its mask, line and overlay apply to that frame. Slide settings override presentation settings. Geometry is calculated from the exact bytes embedded after asset resolution, so host resolvers are called once. PNG, JPEG, GIF and WebP dimension headers are supported; unsupported or unreadable dimensions produce a path-specific error rather than a distorted picture. Supply supported raster bytes through imageResolver for other formats.

JPEG EXIF orientations 1–8 are represented by native picture rotation and mirroring. The embedded copy's orientation tag is normalized to 1 to avoid viewer-dependent double rotation. Compressed pixels and other metadata remain unchanged; input data is not mutated. EXIF orientation in other containers, animated playback, SVG/vector assets, effects and lossless crop/orientation import are not covered by this change.

An image block with placement ({edge, size, inset}) bleeds to that edge: its picture is drawn at the band core composes, in core's item order (after the headings, before the flowed blocks, above the watermark, as the preview draws it), and the headings and the other blocks compose in the rest of the slide. A layout's image placeholder may carry the placement for the slide's image.

Image block treatments export from core's normalized geometry as native DrawingML, the mapping 0.14's slide image used:

  • shape becomes the picture's a:prstGeom (rect, roundRect, ellipse or hexagon) with core's guide values.
  • border becomes a centered solid a:ln with a miter join.
  • recolor becomes a:grayscl or a:duotone, followed by a:alphaModFix for opacity, on the blip only.
  • overlay becomes one tagged OPF image N overlay shape directly above the picture, in the frame's shape or as an edge band.

An image block with any of its own fields (fit, focus, a treatment, placement) carries an OPF_IMAGE_V1 shape tag with those fields and the exact native picture geometry; its overlay carries OPF_IMAGE_OVERLAY_V1. On import an unchanged tagged picture becomes the same image block again, with the embedded bytes as its data URI source. An edited, duplicated or ambiguous tagged picture is imported as an ordinary image block and reports invalid-image-provenance; an edited overlay drops only the overlay. Native PowerPoint raster parity for negative a:srcRect insets has not been checked with Office yet.

A deck or slide watermark (design.watermark) exports as one native picture named OPF watermark per slide, after the image background and its overlay, if any, and before all content (placed image blocks included) (the preview's paint order, so a background picture lies beneath the watermark), at the preview's frame: the centered 40% by 40% box at 30%/30% of the slide, fitted without cropping. Its opacity is a:alphaModFix (the object form's opacity, otherwise the preview default 0.08), and its alt text is the asset's alt, otherwise Watermark. design.watermark = false on a slide exports none. An unchanged export imports back as design.watermark (at deck level when every slide carries the same one); an edited picture stays ordinary content and reports invalid-watermark-provenance. An unresolved image reports unresolved-asset; src is required by the schema, so a watermark with no image source is a validation error and not an export diagnostic.

Brand assets, picture bullets and the accent font (spec-gap closure A)

These design fields need the core that composes them (resolveLogo, geometry.logo, item.bulletImage, fontScheme.accent, the organization logo shapes; core 0.18 or the coordinated source). On core 0.11.3 the export carries none of them and is byte-identical to the previous output. Each one is drawn at the box core composes, which the preview draws too (see core docs/design-resolution.md, "Brand assets and layout hints").

  • Cover and section logo (the primary organization's logo, in its full shape; design.logo on the deck or a slide names another organization or shape, var:organization.beta.logo, var:organization.logo.wordmark, or is false for none). One native picture named OPF logo, after the watermark and before the content, fitted without cropping into core's geometry.logo box (56 px high at 720 px, up to four times as wide), anchored at the box's left edge and vertically centered, exactly the preview's xMinYMid meet. Its alt text is the asset's alt, otherwise Logo. A shape given as {onLight, onDark} picks its tone from the slide background (a dark background takes onDark); the exporter's dark test is the one that already chooses the text colour. An unresolved or unreadable logo draws the preview's "Image unavailable" panel in the box and reports unresolved-asset at the organization's logo path (organization.logo.full.onDark; strictAssets throws). Content slides never get one. The picture carries an OPF_LOGO_V1 tag (slide, source path, tone, the exact picture properties): an unchanged export consumes the picture on import (it is not content; the organization, with every shape and tone, and design.logo return from the document and slide records), and an edited, ambiguous or damaged one stays an ordinary picture and reports invalid-logo-provenance. When nothing else restored the organization's logo (an export with provenance: false, or with references-only, which stores no sources), the consumed picture's own image becomes the logo of the primary organization (a new organization named after the deck when there is none), so the logo is never lost; it is the one drawn image for every shape. A PPTX with no OPF tags imports the picture as an ordinary image block and invents no organization.
  • Header and footer logos (image: "var:organization.logo.icon", or var:organization.logo, .stacked, .wordmark, or a partner's var:organization.beta.logo). Core lays the parts of a zone out in one row (image, text, socials, date, each at its natural width, vertically centered, FURNITURE_GAP apart) and resolves the reference for each slide's background, so an {onLight, onDark} shape draws a different asset on a dark slide. The exporter draws every part at part.box; a logo is an ordinary furniture image part (the OPF_FURNITURE_V1 picture tag) whose slide manifest also records the authored reference and the content key of the bytes it embedded (images: {reference, media} per kind.zone, beside parts and definitions, which every released importer validates strictly). It re-imports as the reference while the picture still embeds those bytes and the stored organization still has a logo for it; a replaced picture (Change Picture), a foreign picture, provenance: false and references-only (no stored organization) import it as an ordinary image. A reference whose organization has no logo reports unresolved-content at <zone>.image, draws nothing and still returns as the reference (drawn: false). An asset that cannot be drawn reports unresolved-asset at the organization's logo path.
  • Picture bullets (design.listBullet: image, resolved to the icon logo by core as item.bulletImage). Every entry's first line carries <a:buSzPct val="100000"/><a:buBlip><a:blip r:embed=…/></a:buBlip> in place of the character bullet, so the marker is the text size, like the preview's square marker. PptxGenJS embeds the icon once per slide (a picture named OPF bullet image); packaging removes that picture and keeps the media part and its relationship for the a:buBlip elements. Marker indent and text positions are those of the character bullets. An icon that cannot be embedded keeps the character bullets and reports unresolved-asset once per slide at the logo's path. Import recognizes a:buBlip paragraphs as list paragraphs; design.listBullet returns from the stored design hint (a PPTX without tags has no hint). PowerPoint shows a picture bullet at the picture's own aspect ratio; use a square icon for the preview's square marker. The released 0.11.6 importer does not know a:buBlip and imports such a list as plain text lines.
  • fontScheme.accent. Core resolves the accent role for the slide tag (eyebrow) and the quote body, so their runs carry the accent typeface in a:latin, a:ea and a:cs with the catalog's pitch family when the family is known; theme major/minor fonts, every other run and the theme are unchanged. The accent family appears in docProps/app.xml "Fonts Used" like any other used font; checkTypefaces callers list it in options.fonts. design.fontScheme (with accent) returns from the document tag.

An unchanged export imports every treatment field back. An edited overlay drops only the overlay and reports invalid-image-provenance. An edited picture or effect drops the treatment: the picture stays an ordinary image block. See core docs/image-treatments.md for the vocabulary and the unsupported effects: blur, shadows, soft edges and background removal. PowerPoint's luminance weights for grayscale and duotone are unverified natively.

Tests compare SVG/native fit and crop geometry across nine synthetic raster fixtures and cover all eight JPEG orientations. Keynote 14.4 visually preserves proportions for wide/tall fit/crop and displays all eight orientations correctly. This does not establish Microsoft PowerPoint raster parity or WebP support in every Office version.

The structural export/import corpus gate covers every installed core example (126 decks / 805 slides for core 0.4.0). It explicitly substitutes a bundled fallback font and synthetic images, then checks slide XML, unique native object IDs, finite geometry, table grids and imported slide counts. It does not establish original-asset, typography or viewer fidelity. The focused table and image tests separately exercise measured geometry and real fixture bytes.

Raster media filenames and package content types are derived from the embedded PNG/JPEG/GIF/WebP bytes. A resolver may change the format without preserving an old asset MIME hint; import likewise detects these formats from their bytes. This metadata repair does not recompress images, validate every compressed pixel stream, fetch resources or establish viewer support for each format.

Native viewer check: Keynote 14.4 displays the PNG/JPEG/GIF media-type specimens, but imports an unchanged WebP as an empty rectangle. The default compatible export now converts WebP to a static PNG locally. Keynote displays all six converted specimens, including alpha, EXIF orientation and the first animation frame. Microsoft PowerPoint has not been verified.

Compatible WebP pictures

toPptx defaults to imageFormat: "compatible". After resolving and embedding an image once, WebP bytes are decoded to a static PNG. Alpha and EXIF orientation are retained in the decoded pixels; animated input uses its first frame. Fit/crop is then calculated from the resulting PNG dimensions. The original OPF input and source bytes are unchanged, but the PPTX contains the PNG rather than the original WebP or its metadata.

Set imageFormat: "preserve" to embed WebP unchanged when the receiving application supports it. Other image formats keep their existing export behavior. Conversion errors include the OPF image path; images above 40 megapixels are rejected before compatible conversion. Decoder differences can affect color/alpha rounding, so byte identity across platforms is not promised.

Node conversion lazily loads the pinned open-source Sharp dependency and requires Node 22 or later. Normal package installation must include platform optional dependencies for its native binaries. Browser bundles select a separate browser decoder using local Blob/image/canvas APIs; Sharp and Node code are excluded. Neither path uploads images or fetches asset URLs. Source-preserving export and ordinary PNG/JPEG/GIF operations do not load Sharp.

npm test includes the Node pixel-reference cases and verifies browser bundling. To run the browser pixel checks, run npm run build:browser-check, serve this repository locally, and open /artifacts/webp-fallback/browser/index.html. The page reports 13 checks covering embedded PNG pixels, alpha, EXIF, the first animation frame, fit/crop, resolver calls and DOM canvas fallback. These are browser export checks, separate from the recorded Keynote viewing evidence.

Native background fills

Fixed solid and linear-gradient backgrounds now export as native slide fills, keeping the background editable without rasterizing slide content. Deck defaults, inline theme overrides and per-slide overrides are resolved before export. Solid opacity, gradient stop colors/positions and combined color/background alpha are preserved. Solid, gradient-stop and pattern colors are ColorRefs, resolved like table fills and run colors: a var: variable or a colour-scheme slot or role name (accent2, primary) paints its colour, and a slot the deck theme holds exactly is written as a:schemeClr. The slide's default text contrast follows the resolved colour. Empty and single-stop gradients follow the SVG preview's transparent/solid behavior; descending stop positions clamp to the preceding stop.

Diagonal gradients require a coordinate conversion: the preview uses an SVG object-bounding-box gradient, while native unscaled DrawingML angles use slide coordinates. Export converts both the physical gradient direction and stop interval. Tests compare 990 sample positions from serialized SVG/native properties across 33 gradients and three aspect ratios, plus solid opacity, inheritance, native edits and repeated imports/exports. Integer native angles/positions introduce small rounding differences. The mapping follows the DrawingML linear-gradient angle definition.

Import reads supported native RGB solid/linear fills directly; it uses no hidden source copy. Uniform alpha becomes OPF background opacity, and differing stop alpha uses eight-bit RGBA colors (which can round alpha). Native path gradients, color transforms outside the supported luminance/alpha set, non-default tile/flip geometry and stop intervals outside OPF's fixed-endpoint representation are not imported. Pass fromPptx(bytes, {onDiagnostic: issue => ...}) to observe unsupported-background-gradient with a slide path.

Node 20/24 tests and the 126-deck / 805-slide structural corpus pass. This proves serialization and the mathematical mapping, not native viewer pixels. Keynote 14.4 recognizes the editable native gradients. Twelve captured native PNGs now support 18 comparisons, including a Keynote-generated PPTX import: opaque differences are at most 4/255 per channel (mean below 0.38), and transparent portrait alpha differs by at most 1/255. The checked-in references run in ordinary Node tests without Keynote. Quick Look still renders these specimens as a flat average color, so its thumbnails are not evidence of their native appearance. Microsoft PowerPoint remains unavailable and unverified. Theme-aware native fills and other design decorations remain separate fidelity work.

Pattern and picture backgrounds (FF-25)

Pattern backgrounds export as a native <a:pattFill> with explicit foreground and background colors. OPF presets named by DrawingML's 54 ST_PresetPatternVal values (for example pct5, ltHorz, openDmnd, wave) are written unchanged. As in the SVG preview, a missing foreground uses the slide text color and a missing background uses white; text contrast follows the pattern's background color. The preview's engine id diagStripe has no DrawingML name. It is still accepted and is written as the closest preset, wdUpDiag; new documents should use wdUpDiag so that re-import keeps the name. Any other engine-defined id keeps only its background color, like the preview, and reports unsupported-pattern. Opacity becomes alpha on both colors. Under the FF-24 theme-color rule, a pattern color authored as a scheme slot or role becomes a:schemeClr, with the background opacity as alpha, when the deck theme holds exactly the color the preview draws. Like the preview, pattern colors resolve only hex, so any other name is drawn as the default color and stays literal RGB. Import resolves theme pattern colors to RGB. The SVG preview draws every one of the 54 presets from core's 8 x 8 bitmaps (RR-07), so the preview and this a:pattFill show the same preset in the same colours (test/rr-07-preview-polish.mjs pins it for all 54); the tiles are the pixels measured from desktop PowerPoint (Office 365, Windows, 2026-10-01), not defined by ECMA-376.

code.language and metric.trend export natively from the same core tables the preview reads (RR-07). Code keeps one text box per source line; a language adds coloured runs (theme-derived colours kept at 4.5:1 or more on the #111827 panel) whose text concatenates to the exact line, and an unknown language is exported plain. A trend adds one arrow shape (upArrow, downArrow or rightArrow, alt text "Trend: up") beside the trend word and colours the delta and trend text green, red or neutral; the arrow is part of the metric's provenance group, so the metric re-imports as the one authored payload. scripts/rr07-native-set.mjs <dir> builds the native-check decks, preview PNGs and a manifest.

An image background ({type: "image", src, alt, fit, focus, opacity, recolor, overlay}, or an image source string, a cover image; core composes it as backgroundImage from the slide, deck or theme background) never moves content. Without alt text it exports as the native slide background, an <a:blipFill> for the embedded raster (data: sources, declared asset: ids and imageResolver results): cover (the default) crops the picture to the slide with a:srcRect around the focus point (core's fitImage), contain centers the whole picture with a:fillRect insets, stretch fills the slide, and tile repeats the picture at its intrinsic size (1 picture pixel = 1 reference pixel, 1/96 in) from the top-left corner. With dpi="0", a native tile is sized from the raster's own resolution: PNG pHYs in metres; JPEG JFIF density in inches or centimetres, otherwise EXIF XResolution/YResolution; 96 dpi when absent. The tile scale is that resolution over 96 on each axis. recolor becomes a:grayscl or a:duotone and opacity a:alphaModFix on the blip, as for image blocks. With alt text (the background's own only: a referenced asset's alt does not make a background meaningful) the picture is a full-slide p:pic at the back of the slide, named OPF background slides.N, carrying the text as its descr so screen readers read it; the slide background is then the colour scheme's default background colour, which shows around a contained picture and through a translucent one. The overlay is one shape directly above the picture (OPF background overlay slides.N for a native fill, OPF background slides.N overlay for the picture): the whole slide, or a band along one edge. WebP sources become PNG parts as for pictures. An unresolved image keeps the background color and reports unresolved-asset at the background's path (strictAssets throws). A JPEG EXIF orientation cannot rotate a background fill, so it reports unsupported-background-image-orientation.

Import reads a:pattFill with a DrawingML preset and resolvable foreground/background colors, including slide/layout/master inheritance and theme references, as an OPF pattern background. Import does not guess undefined default colors. Import also reads embedded a:blipFill pictures as an image background with the exact image bytes as a data: URI. Tiles become tile. If the tile scale, offset, alignment, flip or source insets differ from the geometry OPF tile exports (the picture at its intrinsic size), the import reports approximate-background-image. A crop matching the slide aspect becomes cover, with the focus the crop centers (a crop pinned to an edge reads as the edge-most focus with that crop); centered fill insets matching the image aspect become contain; an uncropped fill of another aspect becomes stretch. Padded, offset or effect-bearing fills become the closest fit and report approximate-background-image. a:grayscl and an a:duotone of two literal colours become recolor. An unchanged tagged background picture (OPF_IMAGE_V1) becomes the image background with its alt text, fit, focus, opacity and recolor, and an unchanged tagged overlay (OPF_IMAGE_OVERLAY_V1) its overlay. Linked or unreadable pictures report unsupported-background-image. test/background-fills.mjs and test/background-images.mjs cover export, import, native edits, inheritance and diagnostics. Microsoft PowerPoint rendering of these fills has not been verified natively: the resolution-based tile scale, a tiled p:pic fill, what PowerPoint shows around a contained or through a translucent native background fill (it has nothing behind the slide background), and negative a:srcRect insets.

Theme color scheme

Export writes the deck's resolved color scheme into ppt/theme/theme1.xml a:clrScheme, named after the scheme. The OPF slots map one to one: dark1/light1/dark2/light2 to dk1/lt1/dk2/lt2, accent1-accent6 to themselves, and hyperlink/followedHyperlink to hlink/folHlink. An abstract role fills a slot only when the scheme leaves that slot unset. Previously every export carried the vendored Office palette (accent1 4472C4). When the deck's design.theme resolves (embedded, or in a registered catalog), a:theme and its thm15:themeFamily take that theme's name (else its id). A deck with no resolvable colour scheme writes core's engine default slots under the name OpenPresentation. The rewrite happens during package normalization; the vendored PptxGenJS bytes are unchanged.

Document colors that name a slot or role (accent2, textSecondary, surface, ...) become a:schemeClr in runs, table cell fills and text, and table borders. Theme-slot backgrounds ({type:'theme', slot} or a slot name) do the same. Slide content reaches the theme through the master color map, so dark1, light1, dark2 and light2 are written as tx1, bg1, tx2 and bg2. A reference becomes schemeClr only when the deck theme slot holds exactly the color the slide resolved. PowerPoint has one theme per master, so a slide with its own design.colorScheme (or a slide theme that changes any slot) keeps every color literal, including values that happen to equal the deck theme, so a theme edit never partially recolors it.

Default text follows the theme only where the background does. When a slide's background is an opaque theme reference (light1/light2 or dark1/dark2, the only slots a theme background accepts), the exporter's default and muted text are written as the paired slot: tx1/tx2 on bg1/bg2, and bg1/bg2 on tx1/tx2. This applies only when the resolved color is exactly that deck slot, so a PowerPoint theme switch moves text and background together. It covers headings, body and list text, list markers, metric labels, quote text and footers, header/footer text and placeholders. Text on a literal background, text on a card fill and override slides stay literal.

Table chrome follows the theme too (FF-24c). With no theme override on the slide, the default header fill is accent1, the body fill is the surface slot (bg2, or tx2 on a dark deck), and every cell border is accent5. Each is written as a:schemeClr when the deck theme slot holds exactly that color. Default cell text pairs with its fill: tx1 on a bg2 fill, bg1 on a tx2 fill, and light1 or dark1 (bg1/tx1) on an accent fill, again only when the contrast-selected color is exactly that slot. A named fill, color or border color on a cell (accent2, surface, light2, textSecondary, ...) is a scheme reference, hyperlink and followedHyperlink included, and a literal on the cell stays literal (default text on a literal fill included). PptxGenJS 4.0.1 cannot write hlink/folHlink, so those two are written as a reserved literal (FE01A0/FE01A1, or the next free pair) and rewritten to a:schemeClr in the finished slide part; a document that itself uses every reserved value keeps its link colors literal.

When the deck background is a theme slot other than light1, the slide master gets that background and its title/body/other text styles use the paired text slot. The layout drops its own bg1 background and inherits the master, so slides added in PowerPoint match the theme. light1 decks keep the vendored master.

These stay a:srgbClr:

  • literal hex colors, even when they equal a scheme slot;
  • var:<id> variables;
  • translucent colors;
  • other engine-derived chrome: card fills and borders, chart panels, series and labels, timeline connectors and markers, and image and media placeholders, plus default text on a card or a literal background. These colors are contrast-selected or derived from roles, not named by the document, and are not yet theme references.

Import reads the first slide master's theme and matches it against the host default catalog only (the first of fromPptx's catalogs): recovered bare ids resolve in a document that declares no catalogs. Theme and colour-scheme recovery does not use the foreign-layout default: omitted catalogs and explicit catalogs: [] both leave recovery unregistered. An exact twelve-slot match with one of its colour schemes returns that id; the clrScheme name breaks ties. Otherwise the importer returns inline slots, relative to the scheme the clrScheme is named after when there is one ({id:'boost', accent1:'#123456'}). A theme whose name equals one of its themes' names maps to design.theme only if the package's color scheme or heading/body fonts match that theme (its references resolved in its own group first); otherwise theme-unverified is reported. With omitted catalogs or explicit catalogs: [], or a match only in another registered catalog, the colour scheme imports inline and no theme id is recovered; an FF-32 stored reference still restores. A missing theme, or slots that are not opaque sRGB/system colors, report unsupported-theme-colors on design.colorScheme. Slide colors are still imported as resolved hex. Role overrides such as primary have no theme slot and are not recovered. test/theme-colors.mjs covers all 14 gallery schemes and 4 gallery themes (registered as defaultCatalog), import without catalogs, override, foreign and damaged themes, and the literal-color boundaries. test/table-theme-colors.mjs covers named and default table chrome on all 14 schemes and four backgrounds, literals, override slides, the link colors, re-import and a theme switch in the package. This establishes package structure and round-trip, not PowerPoint rendering.

JPEG orientation on import

fromPptx now preserves native quarter-turns and mirroring for JPEG pictures by writing the combined orientation into EXIF metadata. Existing embedded EXIF orientation is applied before the native transform. This requires no pixel decoder, recompression, upload or new dependency. The original PPTX remains unchanged, and alternative text survives. The eight orientations produced by this exporter restore the exact source JPEG bytes through repeated fit-mode export/import cycles.

When a JPEG has no orientation tag, import either adds a minimal EXIF segment or appends an IFD0 that retains the existing metadata entries, referenced data offsets and next-IFD link. Malformed or full EXIF segments are left untouched and reported. Tests cover both byte orders, embedded metadata plus native transformations, native picture edits, exact compressed-byte retention and independently permuted pixels.

This preserves image orientation, not arbitrary picture geometry. Crop windows, non-quarter-turn rotations, non-JPEG rotations/mirroring and unsupported EXIF structures retain their original image bytes and report unsupported-image-crop or unsupported-image-orientation through FromPptxOptions.onDiagnostic. Picture diagnostic paths identify native picture order, for example slides.0.pictures.0. Import still recomposes OPF layout and does not promise exact native placement, crop, effects, groups or full picture round-trip fidelity. Third-party native viewers may handle already-oriented embedded JPEGs differently; the metadata-before-native composition is the importer contract, not a cross-viewer parity claim.

Inherited native backgrounds (0.2.1)

Since 0.2.1, the importer follows slide → layout → master background inheritance. An explicit slide background, including no-fill or an unsupported fill, takes precedence over inherited content. Solid and representable linear fills resolve theme slots through the master color map and layout/slide overrides; system colors use the saved lastClr fallback. Theme overrides can replace the color scheme or format scheme. Background style references use the original XML order in fillStyleLst/bgFillStyleLst, including placeholder colors and alpha. Theme-slot OPF exports also retain background opacity.

The 0.2.1 importer applies lum, lumMod, lumOff, alpha, alphaMod and alphaOff in XML order, including repeated/interleaved transforms in theme definitions, style placeholders and gradient stops. Following DrawingML’s luminance modulation and luminance offset semantics, luminance adjustments retain hue and saturation, and opacity operations clamp after each step. Colors are rounded to RGB only after the full reference/transform chain. Tint/shade, saturation/hue, gamma and other transforms still produce diagnostics. The regression suite covers 70 inheritance, transform and diagnostic cases; these mathematical tests do not establish native viewer pixel parity.

These colors become explicit editable OPF RGB fills. Import does not preserve a live link to the original PowerPoint master/theme. No external theme URL is fetched. Missing themes, unknown colors, unsupported color transforms, and unsupported fills (including patterns without a DrawingML preset or explicit colors) report unsupported-background-fill (or unsupported-background-gradient for gradients) at the slide background path.

The regression fixtures cover inheritance, overrides, interleaved style lists, repeated export/import, source preservation and observable failures. The style indexes follow the Open XML background-reference definition. They prove conversion semantics, not universal native appearance. Keynote displayed a fixture referencing fillStyleLst index 2 as white; native comparison of other reference forms is still incomplete. Microsoft PowerPoint remains unverified. This work is not included in npm 0.2.0.

Native dimensions retain full precision through import. Premature six-decimal inch rounding could change raster edges even on a 1280-pixel slide. With the local JPEG-aware renderer, all eight complete image-slide PNG previews now match their original OPF previews after native export/import; this remains an OPF-renderer comparison, not a native viewer pixel comparison.

Background-only and empty slides now remain blank during PPTX import; the importer no longer inserts a synthetic “Slide N” title. Speaker notes remain separate from visible content. Native fixture verification covers this behavior using an actual Keynote-exported presentation.

Version 0.2.0 requires Node 20.9 or later for native image decoding. Browser bundles continue using browser-safe entrypoints.

Conditional table text styles (since 0.5.0)

The importer resolves referenced table styles through the archive's presentation relationship, including custom part paths, and accepts inline definitions. It applies whole-table, alternating row/column, edge and corner text styles using Microsoft's DrawingML precedence. Direct list-level, paragraph and run properties override the inherited style. Supported character properties are bold/italic, explicit or major/minor-theme Latin fonts, and the existing RGB/system/theme color and alpha transforms. Font-reference placeholder colors resolve after style inheritance. The table-style list's insertion default does not silently restyle existing tables without a reference.

Version 0.5.0 adds conditional table styles. Missing definitions (including built-in Office IDs whose definitions are absent from the archive) produce unsupported-table-style; no style data is fetched. Unsupported effects and table backgrounds produce unsupported-table-cell-style. Supported conditional solid fills, borders and their theme references use the same precedence as character styles. Native right-to-left geometry reports unsupported-table-direction and retains source cell order. Script-specific font selection, unsupported border geometry/color models and native PowerPoint raster parity remain separate work. Native re-export preserves effective character formatting but does not reconstruct the original style reference or redundant explicit normal flags.

test/table-styles.mjs builds independent native XML fixtures covering band offsets, overlapping first/last flags, all four corners, theme and explicit fonts, placeholder alpha, inheritance/defaults, direct resets, absent/external definitions and repeated conversion. The checked-in test/fixtures/table-styles/conditional.pptx is project-authored test data, not a captured native-viewer reference. After npm run build:browser-check, serve this repository and open /artifacts/native-table-styles/browser/index.html for 11 browser import, preview-trace and re-export checks.

Styled and merged table cells (since 0.5.0)

The importer returns canonical {value, style} cell objects. Values retain supported rich runs, and styles retain direct solid fills/alpha, individual borders, horizontal/vertical alignment and reference-pixel padding. Rectangular native merges use rowSpan/colSpan anchors with explicit null at covered grid positions. Numeric source types cannot be recovered from displayed native text.

Import validates native continuation flags, bounds and covered text before applying merges. A malformed merge falls back to separate styled cells and reports unsupported-table-merge, retaining every source cell's text. If a valid merge crosses a flagged first row, the table keeps that row in its body with explicit formatting and reports table-header-in-body; OPF repeated headers cannot extend into body rows. Mixed/justified paragraph alignment, vertical text, unsupported fills/lines and 3D/diagonal effects remain explicit diagnostics. Unequal native column widths still need canonical representation.

Conditional borders keep the whole-table outer frame separate from interior horizontal/vertical lines. Row/column bands, edges and corners inherit line properties before direct cell overrides. Archive-local line references resolve theme placeholder colors and alpha. Missing references report unsupported-table-border unless an invisible or complete direct line masks them. A merged anchor uses its full span to select outer edges; differing native continuation border segments report unsupported-table-merge-border and retain the anchor border. test/table-border-styles.mjs covers these cases with native XML fixtures and repeated conversion.

Export consumes the same core geometry as SVG. It writes native merged cells and corrects PptxGenJS's padding-unit heuristic and missing dotted/transparent-border options in generated XML. Rich runs retain their own resolved opacity, including opaque overrides within a translucent cell. Tests inspect actual native XML and repeated conversions; native PowerPoint raster parity is not established.

Version 0.5.0 requires core 0.7.0 and renderer 0.5.0 for coordinated previews. npm run test:styled-table runs export/import regressions. For local browser verification, set OPF_CORE_ROOT to the core repository and OPF_RENDER_ROOT to the coordinated renderer worktree when running npm run build:browser-check. Serve the repository and open /artifacts/native-styled-table-import/browser/index.html. Omit those variables to exercise installed package dependencies.

Version 0.8.0 exports editable native lines with explicit four-space tab stops. Boundary tags contain no original source words, so native edits and deletions remain authoritative. XML-unrepresentable scalar controls reject with an actionable path rather than silently losing characters. Twenty wide/portrait measured/estimated export cases, current-text mutation controls and damaged-tag fallbacks pass; native Office acceptance remains separate. The coordinated core browser workflow also verifies editing, undo and exact reimport.

Version 0.9.1 exports core's accepted header/footer text and picture geometry as editable tagged slide shapes. Since RR-11 the footer's first text, date and slide number are native PowerPoint Header & Footer placeholders (core issue87, native header and footer); releases up to 0.11.7 wrote every part as a tagged shape. Dedicated OPF_FURNITURE_V1 tags describe roles, line boundaries, inactive flags and global/local scope. A slide manifest represents empty or disabled definitions without adding a visible shape. Tags contain no original text, image bytes or alt text. Reimport reads those values from current native shapes; complete groups recover literal dates and the authored zone text with its slide-scoped tokens while the current words still match this slide.

A header or footer zone's text is the author's own string (FA-31): generated values are {{ }} variables in it. Each {{slide.number}} is a native PowerPoint slide-number field (<a:fld type="slidenum">) when its line fits one accepted text line, so PowerPoint renumbers it when slides move; the text around it ("A-{{slide.number}}", "Page {{slide.number}} of {{deck.slideCount}}") is ordinary runs. {{deck.slideCount}} (the exported slide count after pagination) and {{slide.section}} are fixed text, because PowerPoint has no slide-count or section field; the deck-wide variables ({{organization.name}}, {{customer}}) resolve before export like any other string. In body text every slide-scoped token is fixed text with the slide's value. Each slide is composed and drawn from core's substituted slide, so text is measured with the real value. A current date (date: true) needs the host's date option (ISO YYYY-MM-DD); it becomes a native datetime1–datetime7 field when its dateFormat matches that en-US field type, and otherwise fixed text with a furniture-date-fixed diagnostic. An eligible date or number range that spans accepted text lines stays static and reports furniture-field-fixed at its source path; no native field is created by merging shapes. A string date with dateFormat is fixed text. The slide manifest records dateFormat settings and, for a zone whose {{ }} tokens are all built-in variables, that authored text as given to toPptx, before the deck-wide pass (templates); it never records rendered dates. Reimport keeps a format or the authored text only while the current native text still matches it (a fixed date that parses back to the same ISO date; the authored text drawn for this slide equals the current words: deck-wide built-ins resolved from the stored organization and speakers, then the slide's number, count and section), and a live date keeps the pattern of its current field type. Otherwise the current words import as literal text, with each native slide-number field as {{slide.number}} (typed words that spell a token are escaped as \{{). Hiding furniture on a title slide is a slide-level design.footer: false/design.header: false, which round-trips as before.

When a supported current-date field wraps across lines, full provenance records a versioned static-date reason, its format and exact line fingerprints, without cached date words. Complete unchanged soft-wrapped lines can then recover date: true and the authored format on reimport, so a later OPF export can use a new host date. The original PPTX date remains static: this does not establish native refresh or Office compatibility. Actual current native date fields take precedence. Edited or cleared text, damaged evidence, older unmarked exports, references-only and provenance: false remain literal/current-content fallbacks. Fingerprints detect changed text; writable tags are not authentication. Native formatting and geometry are not reconstructed.

Missing, duplicated or inconsistent furniture tags fall back to current native content with an invalid-furniture-provenance diagnostic. Social profiles (socials: true) belong to the organization the manifest names by id, which only the stored document record can supply, so a file without it (provenance: false, another tool) imports the profile lines as ordinary text with an invalid-furniture-provenance diagnostic. Inherited definitions become global only when every slide has a valid definition/override and all inherited values agree; otherwise recovered definitions stay local. A moved slide keeps its live slide-number field (it imports as {{slide.number}} and renumbers in PowerPoint). Native typography, positioning, crop and unrelated metadata are not reconstructed; furniture-import-reflow requests review. npm run test:furniture exercises XML conversion and mutation controls. Native Office, visual corpus and clean installed-package acceptance remain separate gates; published tagged-shape furniture (0.9.1 to 0.11.7) does not establish native Header/Footer support.

Native header and footer placeholders (RR-11): the first footer text becomes a ftr placeholder, the first footer date a dt placeholder (a live datetime1-datetime7 field for date: true) and the first footer slide number an sldNum placeholder, each at core's exact box with its furniture tag; the slide master and layout carry the three placeholders and p:hf flags, and the notes master records that notes pages carry a page number only. A zone text with a {{slide.number}} is the sldNum placeholder (its other words are fixed runs); any other zone text is the ftr placeholder. Header parts, socials, images and logos, second text/date/number parts and multi-line text stay tagged shapes (PowerPoint has no object for them). A footer text that shares its zone with a logo is only as wide as its words (core's row layout), so its slide placeholder has that box while the master and layout placeholder spans the zone. fromPptx reads the placeholders back with or without provenance, and a deck changed through PowerPoint's Insert > Header & Footer dialog imports the change. Every deck, with or without a footer, carries the three master and layout placeholders (flags off when unused), so Insert > Header & Footer works on a deck with no footer. The mapping table, the dialog-edit table and the vetoable decisions are in docs/native-header-footer.md.

Master furniture (RR-72): a part with no placeholder (a logo or other zone image, socials, header text, a second footer text) that is drawn the same on two or more slides is written once. It goes on the slide master when every slide that shows such furniture draws it, else on a layout shared by the slides that do (one layout per distinct set, so a logo's onLight and onDark assets get a layout per background tone); a slide that hides it (design.footer: false) uses a layout with showMasterSp="0". A part on one slide only, a part with a live field and a part that slide content covers stay on the slide (the last with furniture-on-slide, because PowerPoint draws master shapes beneath slide content). The lifted shapes keep their furniture tags (with the slot in place of the slide part), and each slide manifest lists them under shared, so fromPptx reads them from the layout or master: a master logo returns as its var:organization.logo.* reference on every slide. Importers before 0.18 ignore shared; shapes on a master or layout without OPF tags (a third-party template) are not imported. See Master furniture.

Multi-series Pareto exports in chartex: "auto" as an editable clustered column chart containing every authored series, with a chart-data-adapted / chartex-fallback diagnostic. This preserves data rather than claiming native multi-series Pareto support. Explicit chartex: "native" rejects it with unsupported-chart-data; single-series native Pareto is unchanged. chartex: "fallback" also retains all series. Native acceptance of any new behavior remains separate.

About

Pure local OPF to PPTX export and PPTX to OPF import tooling

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages