npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@extend-ai/react-pptx

v0.6.0

Published

Browser-native, virtualized React viewer for PowerPoint (PPTX, PPT) and OpenDocument (ODP, ODG) presentations

Readme

@extend-ai/react-pptx

npm version

Browser-native PowerPoint viewing for React, with experimental OpenDocument support.

npm install @extend-ai/react-pptx

React and React DOM are peer dependencies already present in a React application.

Main API

  • ReactPptxViewer renders URLs, Blob, ArrayBuffer, Uint8Array, parsed presentations, or normalized documents.
  • parsePresentation performs native parsing and returns the same normalized model the viewer renders.
  • initWasm and setWasmSource support eager initialization and custom Wasm/CDN hosting.
  • PptxViewerController provides navigation, zoom, fit, text search, highlighting, model access, and detached thumbnail rendering.

Import @extend-ai/react-pptx/styles.css alongside the component; the stylesheet is emitted as a separate package asset. Parsing and rendering are client-side; importing the JavaScript package during SSR is safe.

Unknown or degraded content is surfaced through onWarning and showDiagnostics rather than silently removed.

Responsive zoom

The viewer accepts the same responsive zoom shape as @extend-ai/react-docx. Numeric values are percentages, while automatic, fit-page, and fit-width remain active as the viewport changes. For example, the shared API can be expressed as:

<Viewer defaultZoom="fit-width" />

With this package's component and required source, use:

<ReactPptxViewer
  source={file}
  defaultZoom="fit-width"
  onZoomChange={({ level, resolvedZoom }) => {
    console.log(level, resolvedZoom);
  }}
/>

automatic shrinks slides that are wider than the viewport without enlarging them. fit-width may enlarge or shrink to fill the available width, and fit-page uses both viewport dimensions. The controller exposes setZoom(level), getZoom(), and getResolvedZoom(), so a custom toolbar can retain the semantic mode while displaying its current percentage. Zooming in or out from a responsive mode should pass getResolvedZoom() + delta to setZoom(), which switches to a numeric level. The older fitMode prop remains supported for compatibility; contain maps to automatic when the new zoom props are absent.

Supported presentation formats

.pptx and legacy PowerPoint 97–2003 .ppt files are both accepted by the same source prop and parsePresentation() API. Legacy files are opened natively from OLE compound storage by the Rust/Wasm parser; the browser does not need LibreOffice, a server-side conversion endpoint, or a temporary .pptx copy.

For .ppt, the parser follows the Current User/UserEditAtom edit chain and live persist-object directory to recover the actual slide list. Supported OfficeArt group and shape anchors, text, pictures, and referenced master drawing content are normalized into the same PresentationDocument consumed by the React viewer. PNG/JPEG pictures render directly, while decoded EMF/WMF records use a bounded browser-side image conversion for static previews.

Legacy rendering is static, not full PowerPoint emulation. Advanced effects, editable binary charts and tables, animations, transitions, active OLE objects, and timed media may degrade to a static picture or remain unsupported. Inspect onWarning or enable showDiagnostics to receive explicit degraded-rendering warnings:

<ReactPptxViewer
  source={file}
  showDiagnostics
  onWarning={(warning) => {
    if (warning.code === 'degraded-rendering') {
      console.warn(warning.message, warning.slideIndex);
    }
  }}
/>

The repository regression suite opens a real three-slide PowerPoint 97–2003 file at tests/fixtures/legacy/file-example-250kb.ppt directly in Rust and the playground. It checks slide order, text, normalized nodes, thumbnail navigation, and diagnostics without a conversion step.

Experimental OpenDocument support

OpenDocument presentations (.odp, .otp, .fodp) and drawings (.odg, .otg, .fodg) are accepted by the same source prop and parsePresentation() API. The Rust/Wasm parser detects the format from the bytes and returns document.format as odp or odg. Drawing pages are displayed as slides. These formats are view only.

Supported charts render from their data, and custom drawing shapes use enhanced geometry where supported. Support is experimental: rendering fidelity varies, especially for chart layouts and custom shapes, and some content remains unsupported. See the OpenDocument support guide for details.

WebAssembly asset

Native PowerPoint parsing runs through a WebAssembly module that loads lazily. Most apps can use the default loader. If your bundler or deployment needs to host the binary explicitly, configure it before the first presentation is parsed:

import { setWasmSource } from '@extend-ai/react-pptx';

setWasmSource('https://cdn.example.com/pptx_wasm_bg.wasm');
// A URL, Request, Response, ArrayBuffer/TypedArray, or WebAssembly.Module also works.

The binary is also available as a package subpath for bundlers that support asset URL imports:

import wasmUrl from '@extend-ai/react-pptx/pptx_wasm_bg.wasm?url';
import { setWasmSource } from '@extend-ai/react-pptx';

setWasmSource(wasmUrl);

Configured strings, URLs, request URLs, bytes, and WebAssembly.Module values are forwarded to the parser worker. A Response is supported on the main thread because it cannot be forwarded reliably to a worker. initWasm() optionally accepts the same source when eager initialization is useful.

Examples

See @extend-ai/react-pptx in action in these live Extend UI examples. Click a screenshot to open the demo and integration docs.

PowerPoint Viewer

Explore slide navigation, zoom, and presentation previews.

PowerPoint viewer with a presentation slide and navigation controls

Fonts

Fonts embedded in a .pptx are decoded by the Rust parser and registered before the first slide is rendered. Applications can also provide licensed/self-hosted fonts. The viewer preserves the requested PowerPoint family, then appends Office-compatible and script-complete fallbacks for Latin, CJK, Arabic, Hebrew, emoji, and math glyphs.

<ReactPptxViewer
  source={file}
  fonts={{
    sources: [
      { family: 'Brand Sans', source: '/fonts/brand-sans.woff2' },
      {
        family: 'Brand Sans',
        source: '/fonts/brand-sans-bold.woff2',
        descriptors: { weight: '700' },
      },
    ],
    fallbacks: { 'Brand Sans': ['Inter', 'Arial'] },
    fallbackFamilies: ['Noto Sans', 'Noto Sans CJK SC', 'Noto Sans Arabic'],
  }}
  onWarning={(warning) => {
    if (warning.code === 'missing-font') console.warn(warning.message);
  }}
/>

The viewer bundles shared fallbacks for Calibri, Arial, Times New Roman, Courier New, Aptos and Aptos Narrow. It loads requested families before layout and prefers embedded, caller-provided and installed originals. Each family is a lazy chunk; deploy all generated chunks alongside the viewer. Set fonts.useOfficeFallbacks to false to disable automatic fallback loading. Redistribution notices are in dist/Office-Font-Licenses.txt.

The package does not redistribute proprietary Microsoft fonts. Other families depend on embedded fonts or fonts supplied by the host. resolvePptxFontFamily() and OFFICE_FONT_FALLBACKS are exported for applications that render presentation text outside the viewer.

Custom scroll areas and hooks

Pass the actual scrolling viewport through virtualization.scrollElement. This works with Radix, Base UI, coss, or any host-owned scroll area and makes that element the observer root.

const [scrollElement, setScrollElement] = useState<HTMLDivElement | null>(null);
const viewer = usePptxViewer();
const parsed = usePptxPresentation(file);

return (
  <ScrollArea.Root>
    <ScrollArea.Viewport ref={setScrollElement}>
      {parsed.presentation ? (
        <ReactPptxViewer
          ref={viewer.ref}
          source={parsed.presentation}
          virtualization={{ enabled: true, overscanViewport: 2, scrollElement }}
        />
      ) : null}
    </ScrollArea.Viewport>
  </ScrollArea.Root>
);

usePptxPresentation (usePptxModel alias) separates parsing from rendering, while usePptxViewer exposes the reactive navigation/search/zoom controller. onViewportReady provides the internal slide surface for advanced integrations and test harnesses.

OfficeArt visuals

Pictures, supported shapes, connectors, and saved SmartArt drawings use the shared Rust OfficeArt renderer. SmartArt text retains its saved text frame separately from the shape frame, including independent offsets, dimensions, rotation, and flips. Semantic data without a supported saved drawing remains an explicit layout fallback.

Presentation metadata

The model carries PowerPoint data beyond the drawn slide so hosts can build their own behaviour on it. Slides, layouts and masters expose transition and timing (the parsed p:transition and p:timing parts, including animation nodes, targets and build lists), text runs expose math (Office Math with a MathML rendering) and field (slide numbers, dates and other fields with their cached text), table cells expose merge, and every node may carry clickAction, hoverAction, editing (locks and connector endpoints), accessibilityTitle, description and decorative. Media nodes expose playback trims and bookmarks, a poster frame, and embeddedObject details for OLE objects and controls. The viewer renders math as MathML, skips merged cells, navigates presentation jump and slide-link actions in place, and shows poster frames with a play control; it does not play transitions or animation timelines.

parsePresentation(source, { include3dModels: true }) includes original embedded 3D model bytes and content type, model and preview relationships, and scene XML in ImageNode.model3d. The option also works through ReactPptxViewer's parseOptions and the parsing hook. It defaults to false; cached Office raster previews remain the rendered appearance. This metadata does not add an interactive 3D renderer.

Cold-start thumbnails

createPptxThumbnailRenderer renders directly from a ParsedPresentation; it does not mount or initialize ReactPptxViewer. One renderer reuses its font manager, DOM/SVG rendering context, and presentation asset cache across slide requests. The parsed presentation can later be passed to ReactPptxViewer without fetching or parsing it again.

import { createPptxThumbnailRenderer, parsePresentation } from '@extend-ai/react-pptx';

const presentation = await parsePresentation(source, {
  onPerformanceMeasurement: recordMeasurement,
});
const renderer = createPptxThumbnailRenderer(presentation, {
  fonts: {
    loadEmbeddedFonts: false,
    waitForFonts: false,
  },
  concurrency: 2,
  onPerformanceMeasurement: recordMeasurement,
});

const thumbnail = await renderer.renderSlide(0, {
  maxWidth: 240,
  maxHeight: 135,
  output: 'blob',
  signal: abortController.signal,
});

image.src = URL.createObjectURL(thumbnail.data);
renderer.destroy();

The output can be blob (PNG, the default), canvas, imageBitmap, or svg. renderSlides() accepts an explicit index list, preserves its order, and never initializes other slides. Both single and batch requests use the renderer's bounded queue. By default, the thumbnail-specific font policy skips embedded fonts, global font readiness, and missing-font probing; callers can opt back into any of those behaviors through fonts.

Performance observers receive download, wasm-parsing, embedded-font-preparation, slide-dom-svg-rendering, image-decoding, and raster-encoding measurements as applicable. Each render result also contains its slide-specific measurements.

Thumbnail hook

usePptxViewerThumbnails exposes detached slide previews for a consumer-owned filmstrip. PPTX thumbnails use the same live DOM/SVG renderer and loaded fonts as the main viewer, so attach each stable containerRef to a regular element rather than a canvas.

import { ReactPptxViewer, usePptxViewer, usePptxViewerThumbnails } from '@extend-ai/react-pptx';

export function PresentationWithThumbnails({ file }: { file: ArrayBuffer }) {
  const viewer = usePptxViewer();
  const { thumbnails } = usePptxViewerThumbnails(viewer.controller, {
    resolution: { maxWidth: 180, maxHeight: 120 },
    renderWindow: {
      visibleSlideIndexes: [0, 1, 2],
      prefetchSlideIndexes: [3, 4, 5],
    },
  });

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr', gap: 16 }}>
      <aside style={{ display: 'grid', gap: 12, alignContent: 'start' }}>
        {thumbnails.map((thumbnail) => (
          <button
            key={thumbnail.slideIndex}
            type="button"
            onClick={() => void viewer.controller?.goToSlide(thumbnail.slideIndex)}
          >
            <div
              ref={thumbnail.containerRef}
              style={{
                width: thumbnail.width,
                height: thumbnail.height,
                overflow: 'hidden',
              }}
            />
          </button>
        ))}
      </aside>
      <ReactPptxViewer ref={viewer.ref} source={file} mode="slide" />
    </div>
  );
}

The result also provides renderThumbnail(slideIndex, element) for imperative integrations and rerenderAttachedThumbnails() for refreshing every currently attached preview. disabled keeps the metadata stable while pausing and disposing thumbnail rendering. For large decks, virtualize the rail against its actual scroll element: mount containerRef only for virtual rows, pass those rows through renderWindow.visibleSlideIndexes, and use prefetchSlideIndexes to warm nearby slides. The built-in showThumbnails filmstrip follows the same windowed behavior.

Block and word citations

The bundled runtime uses the Rust 0.4.1 parser and block extractor, with rust-odf 0.2.0, rust-office-art 0.1.9 and rust-office-charts 0.2.0. Call extractPresentationBlocks(parsed.document, { includeWords: true }), then pass its blocks or words to PptxBlockHighlights or PptxWordHighlights. The components accept a viewer rootRef, selected target, optional multiple targets, color and an onGeometry callback for the selected target, following the React DOCX API.

resolvePptxSourceGeometry, resolvePptxBlockGeometries and resolvePptxWordGeometries return axis-aligned slide-relative fragments with explicit rendered, unmounted, invisible or unresolved states. revealPptxSource(controller, root, ref) mounts and navigates to the referenced slide. normalizedPptxBoundingBox converts a fragment to fractional slide coordinates.

References carry document identity, slide and node paths, optional cell/paragraph indices and UTF-16 text offsets. Preserve them with extracted content; matching visible text alone cannot distinguish repeated occurrences. Geometry is limited to mounted slide content. Notes, chart-label subranges and mathematical subranges do not receive invented rectangles.

See the citation guide for usage and the playground Blocks view.