Skip to content

PDF

This enhancer lets you render PDF documents. The reason it's not part of the core is because PDFs require an extra dependency and have a rather specific & much different way to be rendered. Having it as a core would increase the core size significantly and increase complexity while not everyone needs to render pdf.

The enhancer will hook into the resource loading and rendering process to handle the pdf for you. No resources will need to be fetched through HTTP requests.

Terminal window
npm install @prose-reader/enhancer-pdf "pdfjs-dist@>=6.2.108 <7"

The enhancer declares pdfjs-dist as a >=6.2.108 <7 peer dependency. The floor is the patched release, not the major: every pdfjs-dist from 5.6.83 up to but excluding 6.2.108 is affected by GHSA-hq66-cqwq-w95j, which lets a malicious PDF execute arbitrary JavaScript, and that range includes 6.0.x and 6.1.x. pdfjs-dist 6 also requires Node >=22.13.

The following integration example is simplified and does not use streamers. The prose reader demo uses a client streamer to streamline the process and have a more unified way to stream documents but it's not required. Since the package provides an archive creator helper it can be used as any other archives.

import { of, from } from "rxjs"
import { pdfEnhancer, createArchiveFromPdf } from "@prose-reader/enhancer-pdf"
import { generateManifestFromArchive } from "@prose-reader/streamer"
import pdfWorkerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url"
// You need to import inline the css of the pdf viewer. It is needed
// by the enhancer to inject it in the document and have annotation layer.
// The css is big so we don't embed it in the source code.
import pdfjsViewerInlineCss from "pdfjs-dist/web/pdf_viewer.css?inline"

// Initialize the pdfjs worker somewhere in your code. 
// Something like an index file or your streamer may be a good idea. Please check 
// https://mozilla.github.io/pdf.js/
// for more information. Below is an example with vite bundler.
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(pdfWorkerUrl, import.meta.url).toString()

const createAppReader = pdfEnhancer(createReader)

const run = async () => {
    // This is a simplified way for example purpose.
    // You may retrieve your document in different ways
    const pdfBlob = await localforage.getItem(`myPdf.pdf`)
    // Once you have an archive, you can use it to
    // generate manifest and or provide it to the enhancer.
    // It is not required per se but will blend better with prose flow
    const archive = await createArchiveFromPdf(pdfBlob)
    
    const reader = createAppReader({
        // The generated manifest will have uri that matches the
        // archive files. If you let it by default, the 
        // enhancer will be able to automatically retrieve the pages
        manifest: await generateManifestFromArchive(archive),
        pdf: {
            pdfjsViewerInlineCss,
            // Assuming we load different books, we want
            // to have whatever check is necessary to tell
            // the enhancer to use the archive on specific
            // items only
            getArchiveForItem: item => {
                if (item.href.endsWith(`.pdf`)) {
                    return of(archive)
                }
                
                return of(undefined)
            }
        }
    })
    
    reader.mount(document.getElementById(`reader`))
    
    reader.destroy$.subscribe(() => {
        // don't forget to close your archive once you
        // are done with it. It will free up the resources
        // used by pdfjs automatically
        archive.close()
    })
}

run()