A couple PDF options for Ghost

Want to embed PDFs in your Ghost site? No problem!

A couple PDF options for Ghost

Users can click it and it'll open or download, depending on their browser settings. No javascript, works in email.

Slightly harder option: Embed your file.

This is just an HTML card containing the code below:

<embed class="pdf" src=
"https://www.spectralwebservices.com/content/files/2024/12/Lab-manual-biochemistry-2-spring-2019.pdf" width="800" height="500">

That's not likely to work in email, so be sure to provide a fallback link to download.

Or using PDF.js:

This gives better control over the layout and styling, plus can provide buttons to page through the document instead of scrolling. This is a javascript-heavy solution, and definitely won't work in email, but it can look great on the web.

    Page: /

Here's the code producing that. I definitely wouldn't paste all that in an HTML card if using it regularly! What I've done here is to find the file URL on the page, so it just magically works – you could have an embedded viewer just load anywhere you use a file card. (Er... it would be wise to check that the file is a pdf first, clearly!)

<script src="//mozilla.github.io/pdf.js/build/pdf.mjs" type="module"></script>
<style>
  #the-canvas {
  border: 1px solid black;
  direction: ltr;
    width: 100%;
}
</style>
<script type="module">
// notice that I'm reading the url for the file from the page itself.
  var url = document.querySelector(".kg-file-card a").href;

  // Loaded via <script> tag, create shortcut to access PDF.js exports.
  var { pdfjsLib } = globalThis;

  // The workerSrc property shall be specified.
  pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.mjs';

  var pdfDoc = null,
      pageNum = 1,
      pageRendering = false,
      pageNumPending = null,
      scale = 1.2,
      canvas = document.getElementById('the-canvas'),
      ctx = canvas.getContext('2d');

  /**
   * Get page info from document, resize canvas accordingly, and render page.
   * @param num Page number.
   */
  function renderPage(num) {
    pageRendering = true;
    // Using promise to fetch the page
    pdfDoc.getPage(num).then(function(page) {
      var viewport = page.getViewport({scale: scale});
      canvas.height = viewport.height;
      canvas.width = viewport.width;

      // Render PDF page into canvas context
      var renderContext = {
        canvasContext: ctx,
        viewport: viewport
      };
      var renderTask = page.render(renderContext);

      // Wait for rendering to finish
      renderTask.promise.then(function() {
        pageRendering = false;
        if (pageNumPending !== null) {
          // New page rendering is pending
          renderPage(pageNumPending);
          pageNumPending = null;
        }
      });
    });

    // Update page counters
    document.getElementById('page_num').textContent = num;
  }

  /**
   * If another page rendering in progress, waits until the rendering is
   * finised. Otherwise, executes rendering immediately.
   */
  function queueRenderPage(num) {
    if (pageRendering) {
      pageNumPending = num;
    } else {
      renderPage(num);
    }
  }

  /**
   * Displays previous page.
   */
  function onPrevPage() {
    if (pageNum <= 1) {
      return;
    }
    pageNum--;
    queueRenderPage(pageNum);
  }
  document.getElementById('prev').addEventListener('click', onPrevPage);

  /**
   * Displays next page.
   */
  function onNextPage() {
    if (pageNum >= pdfDoc.numPages) {
      return;
    }
    pageNum++;
    queueRenderPage(pageNum);
  }
  document.getElementById('next').addEventListener('click', onNextPage);

  /**
   * Asynchronously downloads PDF.
   */
  pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
    pdfDoc = pdfDoc_;
    document.getElementById('page_count').textContent = pdfDoc.numPages;

    // Initial/first page rendering
    renderPage(pageNum);
  });
</script>

<div>
  <button id="prev">Previous</button>
  <button id="next">Next</button>
  &nbsp; &nbsp;
  <span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
</div>

<canvas id="the-canvas"></canvas>

A word of caution:

Ghost's native search isn't going to be able to read your PDF. Actually, it isn't going to be able to read the body of the post, so maybe you're not surprised by this. Still, if you need to be able to return search results that include PDF content because you're posting a lot of PDF-only content, please get in touch. I can load PDF content into Algolia and provide really good full-text and pdf-text search!