TPTruPDF

3D & rich media

PDFs authored by CAD and BIM tools can embed 3D models as /3D and /RichMedia annotations carrying U3D or PRC geometry streams. TruPDF detects them and renders the embedded poster preview — the static appearance the authoring tool baked in — positioned exactly where the 3D region sits on the page.

3D support is poster-based by design: the viewer shows the document's own preview image, not an interactive orbit/pan/zoom scene. When an annotation ships no poster, a neutral placeholder region with a screen-reader label (3D U3D content / 3D PRC content) renders instead, so the page never shows a hole.

What gets detected

AnnotationStream formatsHow
/Subtype /3DU3D, PRCAlways claimed
/Subtype /RichMediaU3D, PRCClaimed only when it carries a 3D stream — video/legacy media rich-media annotations pass through untouched

The geometry stream is classified by magic-number sniff — U3D\0 (ECMA-363) vs PRC (ISO 14739-1). Malformed or missing streams classify as unknown and still render their poster; nothing throws.

Posters honour the annotation's /Rotate entry, so 3D content authored with a rotation displays upright — matching Acrobat and other desktop viewers.

Zero-setup in the viewer

<DocViewer> does all of this automatically. Pages with 3D annotations get an extra positioned layer above the page canvas; the layer is pointer-events: none, so text selection and annotation editing around the 3D region work normally. Documents without 3D content pay zero cost.

In secure mode posters are not painted (canvas-only DRM rules), but detection events still fire so audit logs see the access.

Events

Detection is observable on the engine event bus:

engine.on('threed.detected', ({ pageIndex, kind, format, rendered }) => {
  // kind: '3d' | 'richmedia' · format: 'u3d' | 'prc' | 'unknown'
  // rendered is always false — posters, not interactive renders
});
 
engine.on('threed.fallback', ({ pageIndex, reason, format }) => {
  // reason: 'prc-unsupported' | 'no-renderer-available' |
  //         'parse-failure' | 'no-3dd-stream' | 'secure-mode'
  // e.g. offer a "download original" button for desktop CAD tools
});

threed.detected fires once per annotation per mount; threed.fallback categorises why a poster (rather than an interactive view) was shown — useful for telemetry. A threed.rendered event exists in the typed event map but never fires today, since interactive rendering is not shipped.

Advanced surface — @trupdf/threed

Hosts building custom pipelines (server-side triage, custom page composition) can use the underlying package directly. Everything is pure and framework-light:

import {
  detectThreeDAnnotations, // annotation list → ThreeDAnnotation[]
  classifyStreamFormat,    // Uint8Array → 'u3d' | 'prc' | 'unknown'
  renderPostersForPage,    // rasterise each annotation's poster to a canvas
  posterCanvasToDataUrl,   // canvas → PNG data URL (alpha preserved)
  pdfRectToScreen,         // PDF user-space rect → screen rect
  ThreeDLayer,             // React layer used by the viewer
  PosterFallback,          // single poster / placeholder component
} from '@trupdf/threed';
 
const threeDs = detectThreeDAnnotations(await page.getAnnotations());
if (threeDs.length > 0) {
  const posters = await renderPostersForPage(page, threeDs, {
    annotationModeEnableValue: 1,
    signal: abortController.signal,
  });
}

Each ThreeDAnnotation carries { id, kind, format, rect, posterUrl, rotation, streamBytes }rect in PDF user space (bottom-left origin, points), streamBytes the raw geometry stream when available.

renderPostersForPage accepts an AbortSignal, a scale, and returns a Map<annotationId, HTMLCanvasElement>; dispose the canvases when the page unmounts.

License gating

Painting 3D posters requires the threed license feature (gate.require('threed')). Detection alone — and the no-content / secure-mode paths — need no license, so unlicensed sessions degrade silently rather than erroring on ordinary documents.