Architecture
July 10, 2026 · View on GitHub
@aiden0z/pptx-renderer follows a three-stage pipeline:
- Parse
- Model
- Render
1) Parse Layer
Core modules:
src/parser/ZipParser.tssrc/parser/XmlParser.tssrc/parser/RelParser.ts
Responsibilities:
- Open PPTX ZIP package and read entry files.
- Enforce resource limits (
ZipParseLimits) to reduce DoS surface. - Parse OOXML + relationship targets into safe intermediate structures.
2) Model Layer
Core modules:
src/model/Presentation.tssrc/model/Slide.tssrc/model/nodes/*src/search/TextSearch.ts
Responsibilities:
- Build normalized in-memory presentation model.
- Resolve layout/master/theme inheritance.
- Parse node-level geometry, text, style, and relationship references.
- Optionally defer per-slide node parsing with
lazySlidesuntil render, search, or serialization consumes that slide. - Build model-level text indexes and search results that are independent of mounted DOM.
3) Render Layer
Core modules:
src/core/Viewer.ts—PptxViewer(primary API, extendsEventTarget)src/core/Renderer.ts—PptxRenderer(deprecated v1 wrapper, extendsPptxViewer)src/renderer/SlideRenderer.ts— returnsSlideHandlewith per-slide resource lifecyclesrc/renderer/*Renderer.ts
Responsibilities:
- Convert model into DOM elements per slide.
- Handle list/single-slide render modes via
renderList()/renderSlide(). - Instance-level
open()for one-call parse→build→render (staticPptxViewer.open()delegates to this). - Render lifecycle events:
renderstart/rendercompletebracket every render cycle;slidechangefires after render. - A newer render request supersedes older queued or batched work; stale list batches stop at frame boundaries before appending more DOM.
- Typed
on()/off()helpers and state getters (isRendering,zoomPercent,fitMode). - Manage media object URL lifecycle (blob URLs tracked per-handle and per-viewer).
- Handle internal/external navigation (with URL safety checks).
- Expose external slide rendering, scaled thumbnail preview, and search highlight helpers.
- Render common EMF fallback previews when the file contains embedded bitmap data or,
with optional
pdfjsURLs, an embedded PDF preview.
Async Resource Lifecycle
Each renderSlide() call owns an AbortController. SlideHandle.dispose() aborts
in-flight EMF-PDF work before disposing chart instances and owned blob URLs. Async
renderers must check the context signal before mutating DOM or shared caches, and must
revoke any blob URL that arrives after cancellation. PptxViewer.destroy() disposes its
slide handles before clearing the viewer-level media cache.
PDF.js runs inside a short-lived isolated Worker, with PDF.js using its own nested Worker. Success, worker error, timeout, and cancellation share one cleanup path that terminates the outer Worker. No PDF.js module state is imported or configured on the host page.
Chart Runtime and Distribution
src/renderer/chart/echartsRuntime.ts is the only runtime ECharts registration point. It
imports from echarts/core, registers every supported chart/component plus
CanvasRenderer, and is explicitly declared as side-effectful package code. Type-only
imports may still come from echarts without pulling the full runtime into the bundle.
The normal ESM/CJS builds externalize echarts/* and JSZip for application bundlers. The
./browser entry bundles JSZip and the registered ECharts subset while keeping PDF.js
optional and external.
Rendering Strategies
renderList() supports:
- Default (
windowed: false): mount all slide DOM nodes. - Windowed (
windowed: true): mount near-viewport slides viaIntersectionObserver, with fallback to full mode when unavailable.
This keeps default behavior backward compatible while enabling lower memory pressure for large decks.
For large viewer surfaces, windowed: true pairs with lazySlides: true so off-screen
slides do not pay shape/table/chart parsing cost before the first visible slides render.
For media-heavy decks, lazyMedia: true also keeps package media compressed until a
rendered slide references it.
Search, Highlights, and Scaled Previews
Text search is a model-layer feature. buildTextIndex(), searchText(), and
searchPresentation() read normalized shape, table, and group text from PresentationData
instead of scanning rendered DOM nodes. PptxViewer.searchText() is the viewer-level
convenience wrapper around the same search model.
String queries default to case-insensitive matching and can opt into exact casing with
matchCase: true. RegExp queries keep caller-provided flags; the search layer only adds
g so all matches can be collected.
Search results return TextSearchResult metadata such as slideIndex, nodeId,
nodePath, match offsets, snippet text, and node bounds. The bounds are intrinsic
slide coordinates for the matched shape or table cell owner. This keeps the public API
stable even when a slide is not currently mounted.
highlightSearchResult() is a DOM helper for the common viewer UI case. It draws a
node-level overlay using default highlight styling, and accepts SearchHighlightOptions
for custom class names, border colors, background colors, shadows, padding, radius, and
z-index. The returned SearchHighlightHandle is owned by the caller; call
dispose() or clearSearchHighlights() to remove overlays.
The renderer intentionally does not provide character-level text highlighting today. Mapping match offsets back to shaped Office text runs, wrapped lines, bullets, and vertical text is a separate renderer problem. The current boundary is model-level search plus node-level highlight overlays.
renderThumbnailToContainer() renders a slide at intrinsic size and scales the result
with CSS transforms inside a clipped wrapper. It is a scaled DOM/SVG preview for
navigation surfaces, not a separate bitmap generation pipeline. The caller owns the
returned SlideHandle and must dispose it when the preview is no longer needed.
Design Constraints
- Keep parser/model deterministic for reproducible QA runs.
- Keep rendering resilient: per-node/per-slide failures should not crash the whole deck.
- Keep security boundaries explicit at parse and navigation boundaries.
- Keep optional heavy dependencies such as
pdfjs-distoutside the core render path unless the consumer explicitly configures them.
Non-Goals (Current)
- Full fidelity parity with Microsoft PowerPoint for every OOXML edge case.
- Server-side rendering runtime in this repository.
- Full EMF/WMF vector instruction rendering. EMF support is limited to fallback previews.