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
| Annotation | Stream formats | How |
|---|---|---|
/Subtype /3D | U3D, PRC | Always claimed |
/Subtype /RichMedia | U3D, PRC | Claimed 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.