Highly Efficient PDF Renderer

Hi Folks,

I’ve been working on HEPR (Highly Efficient PDF Renderer), a GPU-first PDF renderer aimed at very large, vector-heavy floorplan PDFs, but it can also handle PDF books with hundreds, or >1000 of pages with ease.

The motivation came from floorplans exported from Revit and similar BIM/CAD tools. These files can be technically valid PDFs, but they often contain huge amounts of vector geometry. In regular PDF viewers, opening them can take a long time, and panning or zooming can become very slow or even crash the viewer.

Most web-based approaches rasterize these PDFs into tiles. That works, but it has tradeoffs: tile generation can be expensive, real-time client-side rasterization can be painful for huge files, and deep zoom eventually becomes blurry unless you generate many zoom levels.

HEPR takes a different approach: it extracts PDF vector data and renders it through WebGL/WebGPU/three.js-friendly GPU paths, so large floorplans can stay sharp while zooming.

It includes:

  • WebGL and WebGPU backends
  • A three.js THREE.Group wrapper
  • Vector strokes, fills, text, and embedded raster image layers
  • Parsed ZIP export/import to skip repeated PDF extraction (much faster to load)
  • Nanite-inspired Vector LOD for reducing rendered stroke segment counts based on the camera/view
  • GPU text rendering ideas inspired by the work shared at https://wdobbie.com/. Thanks to @wjdobbie for publishing those articles and demos.

Demos:

npm:

Github repo:

It is still early, and I would especially appreciate feedback from people who have dealt with large architectural PDFs or PDF rendering inside three.js scenes.

13 Likes

A while ago I was trying to find something like this - that would let show PDF files in threejs. The only thing I found was using pdf.js to render to a canvas and use that as a texture. I was surprised there was not much discussion about other options. (Did I miss something?)

This is very helpful. Essentially, I’m trying to build websites with as little html/css as possible. Finding ways to present pdf files in 3D space, that scale up with the level of detail, etc. is a big benefit.

Wow!

1 Like

So glad you found it useful! Feel free to share your project that uses my lib, I’m interested! Also if you find any bugs/problems, or if you want new features, let me know!

I am building an ERP system that will require a lot of PDF file uploads with three.js objects placed overtop, and I am going to give this a go, as like you said, the rasterized tiles are killing performance. thanks for making this available

1 Like

Excited to hear that! Let me know how it goes, or if you need any help, or encounter any bugs!

1 Like

this looks great! can I ask how this handles unit conversion from any arbitrary pdf unit to three.js units (meters)? would we have to know the unit scale the pdf is drawn in and scale the parent group accordingly? also if a pdf is drawn to scale (say 1:10 but it’s not specified) would you have a suggestion for a convenient way to make the conversion to “real world” units in three?

Hey,

I don’t consider myself an expert on PDFs, but as far as I know, PDFs use “points”, or “dots” for units, and PDF viewers usually open them with 72 dpi (dots per inch), meaning one dot is 1/72 inch. Of course, usually PDFs are not generated with that conversion number in mind, it’s usually quite random, so you probably need some internal info from/about the PDF.

For example, if you know for a fact that the floorplan itself is 20 meters wide, and the PDF you’re trying to render is 2560 dots wide, that means 2560 dots = 20 meters, so 2560/20=128 dots/meter, meaning you need to multiply the scale of the loaded PDF by 1/128 to get the dimensions in meters.

If you don’t have a reference length/width like that, and the pdf doesn’t “respect” the “72 dpi convention” either, then you probably need a tool to mark an A to B line segment on the pdf (like a hallway, a door, or something on a floorplan that you know exactly the dimensions of), and manually providing the real-life measurement unit for that, like “3 feet”. Then, you can freely use that information to scale your pdfs to real size units of your liking, with simple math like in the example above.

Does that help?

Very much so, that makes a lot of sense, thank you! When converting a standard pdf to a texture in three I’m utilizing an A to B point measurement and calculating the world scale of the plane it is on thereafter, I was mainly wondering if your HEPR repo was attempting to manage some sort of calculation like this under the hood and if I’d need to change anything in my current workflow to adhere but I think you’ve answered the question that I can essentially use the same logic and simply apply the calculated scale to the parent of the parsed HEPR representation, if I understand correctly?

Sure, the HEPR representation of the PDF page is just a THREE.Group, you can change it’s scale according to your calculations. Feel free to check the three-demo page and the readme file for examples

1 Like

hey @soadzoor, I’ve just gotten round to installing your HEPR npm package, I’ve noticed the following warning, are you using a static build of three.js in the distribution rather than a reference to “three”?

THREE.WARNING: Multiple instances of Three.js being @soadzoor_hepr.jssoadzoor_hepr.js

EDIT: I managed to use the src files instead and it seems to work well!

Is there a way to specify a single page or range of pages to render from a multi page pdf? eg say there’s 20 pages and I only want to render page 2 or a range / subset of pages eg [1,2,3,5,8]?

EDIT 2: I see there’s a maxPages option for PdfObjectGenerator that’s neat, I can’t seem to find a way to specify which page / s, maybe that’s not possible as of yet?

EDIT 3: I went and modified the source files locally to support picking a subset of pages to render, Ill try getting round to creating a pr if you’d be interested in supporting the functionality, it’s quite straight forward, I just setup passing an array of values as “pages” and using the same logic as the maxPages for loop but using the pages array current value as the pageNumber and ensuring the current index and the current pageNumber are not more than the extractedPageCount…

This supports any single page eg [1], [8], etc, any range eg [1,2,3], [6,7,8], etc and any subset of pages eg [2,4,6], [3,6,9], etc…

Let me know if this is useful

Thank you for your feedback!

Bare three was externalized, but three/webgpu was accidentally bundled, embedding a second Three.js core. This is fixed now.

For the page ranges, I’ve published an improvement based on your comment/question, now it works like the “custom pages” options in print settings, so it accepts ranges, and specific pages, separated by comma: e.g. “1-5, 8, 11-13”

Also, there were some confusions around public APIs (which one to use for loading the PDF), so I made some modifications there, now the only function exposed is “pdfObjectGenerator”, that’s what you should use.

const pdfObject = await pdfObjectGenerator(source, {
pages: "1-5, 8, 11-13"
});

Please try to install the latest version (0.1.15), and let me know!

2 Likes

that’s amazing thanks for the rapid update! I had a feeling you’d be quick to get a solution together for the page ranges, neat work! I’ll try the latest vesion out!

yes I assumed pdfObjectGenerator is all that’s needed to load pdf’s to three as it is in your MD usage instructions but yes index.ts has a few other things going on that made it a little tricky to read.

Thanks again for the updates on this, great work!

Hey @soadzoor I’m wondering what the process is to ZIP a PDF to be compatible with HEPR, i tried with a standard zipped PDF but it gives an error “missing maniest.json” is the zipping / export process part of the HEPR lib?

@Lawrence3DPK The ZIP format in HEPR is not just a PDF that is compressed into a ZIP file, it has most of its data parsed already, and optimized for the viewer, so it takes less space, and more importantly, it loads much faster than PDFs.

You can try and load and PDFs into the viewer, and then use the “Download Parsed Data” button to get / download the optimized PDF in ZIP format.

You have a valid point there though: the functionality that runs when you click that button was not exposed through the package, so I’ve quickly made a new version for that to be available: 0.1.16

If you upgrade to that version, you can use the functionality somehow like this:

`import { buildParsedDataZip } from “@soadzoor/hepr”;

// URL, File, Blob, Uint8Array, ArrayBuffer, base64, or data URL
const zipBlob = await buildParsedDataZip(pdfSource);`

Let me know whether that works for you, or if you have any further questions, thoughts for improvements! Thank you!

1 Like

OK yeah I thought there may be an internal process to generate the zip files after failing with standard zipping, thanks for exposing this functionality that’s really useful! The reason behind the request is that if an app uses HEPR then the files may as well be stored as zips to save space so this will work great, another question would be, if the files are stored in zip format and a user wanted to download the original pdf is there a way to convert from the zip file back into an original pdf format? would this be possible or would you suggest just storing the original pdf’s for backwards compatibility?

I believe it’s a one way street. There might be a possibility to create a PDF from the parsed zip, but it can’t be guaranteed that it would contain all the details from the original PDF properly. There are quite some PDF features HEPR simply doesn’t support (and the parsed data ignores, discards), like input fields, interactive elements, and also layers are not guaranteed to be rendered in the right order (yet?).

So for backward compatibility, I’d strongly suggest you download/upload the original PDF along with the ZIP

1 Like

Hi, nice Tool :ok_hand:
There still seem to be some issues when the PDF contains a lot of images(?):
Example: Downloads: Broschüren und Anleitungen für Betreiber | Solvis GmbH

Error:

ncaught (in promise) TypeError: can’t access property “restore”, this.ctx is undefined

restore https://soadzoor.github.io/Highly-Efficient-PDF-Renderer/assets/pdf-BrkPXbfk.js:44

#u https://soadzoor.github.io/Highly-Efficient-PDF-Renderer/assets/pdf-BrkPXbfk.js:44

endDrawing

@agrafikr Thank you for reporting this!

I submitted a fix for this problem, make sure to fetch the latest version (0.1.17), and try again!

I also updated the demo pages, so they can render the PDF now without errors.

However, I noticed some visual problems on the same PDF (gradients and dashed lines are not respected in my renderer), so I’m currently working on those.

2 Likes

@agrafikr I made quite some improvements on the renderer to support more PDF elements (gradients, object-rendering order more accurately, etc), so overall, the fidelity has improved now a lot. Please try the newest demos, and the newest version (0.1.18), and let me know whether you find anything else to be improved/fixed!

1 Like

It looks great, thank you. :ok_hand: