@soadzoor/hepr
v0.1.71
Published
Highly-Efficient-PDF-Renderer is a GPU-first PDF renderer for large technical documents, floorplans, books, and more, using the latest technologies and various tricks to achieve high performance and visual fidelity.
Maintainers
Readme
Highly Efficient PDF Renderer (HEPR)
GPU rendering for detailed floorplans, technical drawings, and entire books — in a standalone viewer or your three.js scene.
HEPR brings PDF vector content, text, and embedded images to the GPU. WebGL and WebGPU backends, adaptive level of detail, and a reusable .hep document format help keep detailed documents practical to explore.
Live demo · Examples · Manual · API · Documentation · Changelog
Try it
Open the standalone viewer or the three.js demo. Choose a bundled example or drop in a PDF or HEP file, then pan, zoom, search, and select text. Both demos offer WebGL and WebGPU modes.
Features
- GPU vector rendering: strokes, fills, and text stay sharp as you zoom; embedded images and composited PDF content are supported too.
- Adaptive detail: vector and text LOD reduce work when detail is too small to see.
- Three.js integration: add a
THREE.Groupto your scene and use your camera and controls. - Multiple pages: load a whole PDF or selected pages, arranged in a grid.
- Search and selection: find text, highlight matches, and copy selections on desktop and touch devices.
- Drawing selection: select drawing elements, inspect their geometry and layer membership, and temporarily recolor vectors.
- PDF layers: toggle optional content in the main viewer, inspect layer dependencies when picking geometry, or use the shared library APIs.
- Reusable documents: export
.hepfiles with geometry, images, and a searchable text index to skip PDF parsing on subsequent loads. - Fast thumbnails: prepare one PDF/HEP page for its requested pixel size, with cancellation, lossless WebP output, and reusable thumbnail-to-HEP sessions.
Thumbnails
import { renderPdfThumbnail } from "@soadzoor/hepr/bundler";
const { blob, width, height } = await renderPdfThumbnail(file, {
page: 1,
maxDimension: 400,
background: "transparent",
signal: controller.signal,
});Thumbnails prioritize a quick overview: fine text and geometry can be approximated
at the requested resolution. WebP encoding is lossless; full-quality HEP conversion
keeps its normal preparation settings. Use
openDocumentSession to share PDF
parsing and font resources between thumbnails and HEP conversion.
For Node.js, import the same thumbnail and session functions from
@soadzoor/hepr/node. Install the optional Canvas2D backend for server rendering:
npm install @soadzoor/hepr @napi-rs/canvasimport { writeFile } from "node:fs/promises";
import { renderPdfThumbnail } from "@soadzoor/hepr/node";
const { blob, width, height } = await renderPdfThumbnail("drawing.pdf", {
maxDimension: 400,
background: "transparent",
});
const outputPath = blob.type === "image/webp" ? "drawing.webp" : "drawing.png";
await writeFile(outputPath, new Uint8Array(await blob.arrayBuffer()));The Node API accepts PDF paths, file URLs and bytes, uses Canvas2D and Node
workers, and requires no browser globals. Its reusable openDocumentSession
also supports later full-quality HEP conversion. HEP input thumbnails remain
available through the browser API. See Node thumbnails
for cancellation, source and worker options.
See it in action
Toggle PDF layers
Show or hide individual layers (PDF OCG) to focus on the parts of a drawing you need.

Zoom into every detail
Vector graphics stay vector. Zoom deep into a detailed floorplan while lines and curves stay sharp and navigation stays smooth.

Explore whole books
Load an entire book, then find, select, and copy text with crisp rendering and smooth navigation. Here, HEPR handles War and Peace.

Quick start
Install the browser package alongside three.js:
npm install @soadzoor/hepr threeFor browser apps built with Vite, use @soadzoor/hepr/bundler throughout your
application and add these settings to your existing Vite configuration:
import { defineConfig } from "vite";
export default defineConfig({
optimizeDeps: { exclude: ["@soadzoor/hepr/bundler"] },
worker: { format: "es" }
});No HEPR plugin or manual asset copying is required. See package entry points for other bundlers, prebuilt assets, and Node usage.
Serve a PDF at /document.pdf and add this to your entry module:
import * as THREE from "three";
import { MapControls } from "three/addons/controls/MapControls.js";
import { pdfObjectGenerator } from "@soadzoor/hepr/bundler";
const width = 800;
const height = 600;
const aspect = width / height;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0.1, 100);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
const controls = new MapControls(camera, renderer.domElement);
controls.enableRotate = false;
controls.screenSpacePanning = true;
controls.update();
const pdf = await pdfObjectGenerator("/document.pdf");
// HEPR centers the document at the origin. Fit its longest side into the view.
const bounds = pdf.sceneData.pageBounds;
const size = Math.max(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY, 1);
pdf.scale.setScalar(1.8 / size);
scene.add(pdf);
renderer.setAnimationLoop(() => renderer.render(scene, camera));Drag to pan and scroll or pinch to zoom. The responsive viewer example adds window resizing and complete viewer cleanup.
Individual pages also expose normal Three.js transforms on both backends:
const page = await pdf.getPage(0); // Zero-based displayed page index
page.position.z = 20;
page.rotation.y = Math.PI / 6;
page.scale.setScalar(0.8);
// Also available: getPages(), setPagePosition(index, x, y, z),
// and setPageTransform(index, matrix4).Page views preserve the initial layout and use the page center as their pivot. Compatible pages keep cross-page batching while moving independently, using a shared GPU transform table on both Three.js backends. Paint-order constraints automatically use separate page rendering when needed. See the page API for matrix, ownership, coordinate and HEP compatibility details.
The same loader accepts .hep files, File/Blob objects, bytes, and base64 data. Select PDF pages with { pages: "1-3, 5" }; report loading progress with { onProgress: ({ stage, value }) => console.log(stage, value) }.
When removing a document, call pdf.removeFromParent() and pdf.dispose(). When closing the viewer, also stop its animation loop and dispose its controls and renderer. See the examples to add search, text selection, and drawing selection.
Render your own polylines
Use buildStrokeScene and createThreePdfObject to render your own polylines
with HEPR's stroke batching and camera-driven LOD, without a PDF. See the
polyline example
for geometry, placement, and cleanup.
Save a HEP file
Build a reusable document from a PDF, or export the scene you already loaded:
import { buildHep } from "@soadzoor/hepr/bundler";
const hepBlob = await buildHep("/document.pdf");
// Or: const hepBlob = await buildHep(pdf.sceneData);
// Save or upload the Blob with a .hep filename.For conversion followed by immediate display, use buildHepWithScene() (also
available on document sessions) to receive { blob, scene }. Pass that scene to
createThreePdfObject() without decoding the generated HEP; applicable LOD
caches are reused. The scene remains usable after the session closes and retains
full-resolution image pixels, which can use more memory than a blob-only export.
HEP skips PDF extraction. buildHep includes applicable vector and text LOD caches
by default to skip expensive simplification on load. Set withVectorLod: false
and/or withTextLod: false to omit them. Shared vector records reduce file size;
spatial indexes and GPU resources are prepared when opening the file. Stored
vector LODs default to compact precision, with bounded
rounding of derived positions; the exact scene stays unchanged. Set
vectorLodPrecision: "lossless" to disable additional rounding.
See the manual for browser export and Node conversion.
Convert a local PDF or a directory of PDFs from the command line:
npx @soadzoor/hepr ./input.pdf --output-dir=./heps
npx @soadzoor/hepr ./pdfs --workers=4 --output-dir=./hepsThumbnail CLI modes are available in this checkout. Until a version containing them is published, use the local launcher with the compiled library prepared:
node bin/pdf-to-hep.js ./drawing.pdf --output=thumbnail
node bin/pdf-to-hep.js ./pdfs --output=both --thumbnail-max-dimension=400 --output-dir=./output--output=hep remains the default. Thumbnail output defaults to page 1, a
400-pixel longest side, lossless WebP and a white background; for example,
drawing-thumbnail-p1.webp. Select PNG, another page or a transparent background
with --thumbnail-format=png, --thumbnail-page=2 and
--thumbnail-background=transparent. both reuses the document session for
the thumbnail and canonical HEP conversion. Existing HEP and thumbnail outputs
are skipped independently; use --force to regenerate them. See the
CLI thumbnail options for details.
The CLI includes applicable vector and text LOD caches by default, matching
buildHep and both example viewers. Use
--without-vector-lod or --without-text-lod to omit either cache, or both flags
for smaller files without stored LODs. Stored vector LODs use compact precision
unless --vector-lod-precision=lossless is supplied.
The CLI requires Node.js 22.15+, 23.5+, or 24+. It writes
<name>-parsed-data.hep and supports the same conversion flags as the repository's
node PDFtoHEP.js command. After installing the package in a project, use
npx -- pdf-to-hep --help to see all options.
Conversion batches save detailed worker output, warnings, errors, full file paths, and
raster-fallback diagnostics to pdf-to-hep-<timestamp>-<unique>.log in the current
directory. Use --log-file=./conversion.log to choose a log file; an existing file
is appended to. For HEP-only runs, the terminal shows progress, followed by a
table of filenames, statuses, durations, peak RSS, PDF and HEP sizes, and
percentage size changes.
Peak RSS is the highest resident memory of the worker process that converted the
PDF, including its runtime and parser thread; copied duplicates show none. Smaller
HEPs are green and larger HEPs are red when the terminal supports color and
NO_COLOR is unset. The final metrics table includes attempted, successful, failed
and skipped counts, warning and raster-fallback counts, total conversion time,
average and median attempted conversion times, the highest worker peak RSS, batch
wall time, and successful size totals. Existing-file skips are excluded from timing and size comparisons;
--force --keep-unchanged conversions count as successful even when the existing
HEP is kept. Summed conversion times can exceed wall time when workers overlap.
PDFs with identical content (same size and SHA-256) are converted once per batch;
the other copies receive a copy of that HEP and are reported as duplicates.
Successful copies count toward attempted and successful PDFs, both file-size
totals, and raster-fallback counts. Timing statistics include their actual copy
time; estimated conversion time saved is reported separately. Warning totals
count diagnostics actually emitted during the run.
Conversion requires @napi-rs/canvas, which npm installs as an optional dependency
by default. If its installation fails or optional dependencies were omitted,
install both packages with npm install @soadzoor/hepr @napi-rs/canvas and run
npx pdf-to-hep from that project.
Current HEP readers support scene schemas v9–v16. Exports select the version required by their stored features. Eligible oversized JPEG 2000 images retain their original compression and full-resolution pixels in v16, without another lossy encode; only exports using this storage require a v16-capable reader. Regenerate archives with scene schemas older than v9 from the original PDFs. Exports retain hidden content and original layer defaults; temporary layer visibility and primitive colors remain per-view settings.
Learn more
| Page | What you will find | | --- | --- | | Examples | Live demos and integration recipes. | | Manual | Loading, rendering options, interaction, and HEP conversion. | | API reference | Public functions, options, and object methods. | | Documentation | How rendering works and links to technical references. | | Development | Local setup, builds, tests, and example assets. | | Changelog | Release notes, compatibility changes, and upgrade guidance. |
Requirements and scope
Use a modern browser with WebGL2, or WebGPU support for the WebGPU backend. The browser package uses browser canvas APIs; Node PDF conversion may also need the optional @napi-rs/canvas dependency.
HEPR prepares all selected pages before returning a document. It prefers usable output over rejecting a PDF: supported content stays vector-based, while some pages use bounded raster fallback or diagnosed visual approximations. Rasterized pages lose vector sharpness and drawing geometry; searchable text is retained where available. Encrypted PDFs are decrypted, using the password option when one is required. Features unsupported by both compilation and rendering still produce errors. See the manual for platform and format details.
Contributing
Bug reports and focused contributions are welcome. For rendering issues, include a reproducible PDF when possible, the affected page, browser, and backend in an issue. Start with the development guide for local checks.
License and credits
MIT. Inspired by Will Dobbie's GPU text rendering work and Unreal Engine's Nanite. Third-party attributions are listed in THIRD_PARTY_NOTICES.
