Displaying PDFs?

Sometimes we’ll have a client ask to ‘display a PDF’ on a site, with I think their expectation that they’ll be able to just upload it and display on a page as if it was an image.

In these cases I normally just do a few exports of that PDF to PNG, add those to the page and link the image blocks to the original PDF for download.

But it got me revisiting how to embed PDFs, as over the years the support and methods have changed a little.

As a quick test I created a template for the File block, using an object tag:

<?php defined('C5_EXECUTE') or die('Access Denied.');

$c = Page::getCurrentPage();
$f = $controller->getFileObject();
$fp = new Permissions($f);

if ($f && $fp->canViewFile()) {?>

<div style="height: 0;padding-bottom: 147%; width: 100%; position: relative">
    <object type="application/pdf" data="<?= $f->getURL(); ?>"
            style="border: none; position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100%;"
            title="<?php echo h(stripslashes($controller->getLinkText())) ?>">
        <a href="<?php echo   $f->getForceDownloadURL() ?>">
            <?= t('Download'); ?> <?php echo h(stripslashes($controller->getLinkText())) ?>
        </a>
    </object>
</div>
    
    <?php
}

if (!$f && $c->isEditMode()) {
    ?>
    <div class="ccm-edit-mode-disabled-item"><?= t('Empty File Block.') ?></div>
    <?php
}

That’s been hardcoded to be roughly the right dimensions for an A4 portrait document, and is responsive. That works on Chrome, Firefox and desktop Safari. Using an iframe or embed tag seems to work just as well.

But on iOS, it renders the first page, but with no controls, and no ability to scroll to the second page. (i’m not surprised, as iOS traditionally hasn’t supported PDFs in the browser). It doesn’t even fall back to the link inside the object tag, it just pretends to be able to support inline PDFs.

So how do you approach it, especially with Concrete?

There’s an approach using https://drive.google.com/viewerng/viewer, and I’ve used an Adobe script approach in the past as well… but a ‘native’ approach without third parties would be preferable I’d think.

There’s also https://pdfobject.com, which really does browser detection to force fallbacks.

Thoughts?

I’ve used pdf.js to display PDF files to website visitors (I think that’s also the viewer used by firefox).

I’ve ended up using pdfobject.js

In case anyone stumbles across this, this is what I did:

  • I created a new template for the File block. I did this in a package, but it could be added at /application/blocks/file/templates/pdf_display
    Screenshot 2024-08-12 at 17.48.56

  • In view.js, I put a copy of the pdfobject.js script - https://unpkg.com/pdfobject@2.3.0/pdfobject.min.js

  • in view.php I used this code:

<?php defined('C5_EXECUTE') or die('Access Denied.');
$uniqueid = app()->make('helper/validation/identifier')->getString(10);
$page = \Concrete\Core\Page\Page::getCurrentPage();
$f = $controller->getFileObject();
$fp = new \Concrete\Core\Permission\Checker($f);

if (is_object($page) && $page->isEditMode()) { ?>
    <div class="ccm-edit-mode-disabled-item"><?= t('PDF Display') ?> - <?= h($controller->getLinkText()); ?></div>
<?php } else { ?>

<?php if ($f && $fp->canViewFile()) {?>
    <div id="pdfview-<?= $uniqueid; ?>"></div>
    <script>
        window.addEventListener('DOMContentLoaded', (event) => {
            PDFObject.embed("<?= $f->getURL(); ?>", "#pdfview-<?= $uniqueid; ?>",
                {
                    title: <?= json_encode($controller->getLinkText()) ?>,
                    fallbackLink: "<p><?= t("View PDF - <a href='[url]'>%s</a>", $controller->getLinkText()); ?></p>"
                }
            );
        });
    </script>

    <?php }
    if (!$f && $c->isEditMode()) {  ?>
        <div class="ccm-edit-mode-disabled-item"><?= t('Empty File Block.') ?></div>
    <?php } ?>
<?php } ?>
  • and finally in view.css I put:
.pdfobject-container {
    height: 0;
    padding-bottom: 147%;
    width: 100%;
    position: relative
}

.pdfobject-container iframe {
    border: none; position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100%;
}

In desktop browser, you get a nice responsive iframe of the PDF, e.g.:

But in mobile Safari, it falls back to this more appropriate download link:
Screenshot 2024-08-12 at 17.52.19

If the template is accidentally used for a different file type, like an image, nothing should break as such, it’ll just display it in an iframe.

Just thought I’d share, since it’s an easy thing to drop into a site.

1 Like

Very helpful! Thank you for sharing :pray:

@mesuva any reason to choose pdfobject instead of pdfjs?

Because they are very different scripts, and for the purpose of just embedding PDFs on a site I elected to go with the more ‘native’ approach:

  • pdfobject is a lightweight script, and really is there just to detect when a browser doesn’t do a good job of natively embedding (i.e. mobile Safari). Pdf.js in contrast is a lot heavier, but is already there in Firefox.
  • I think it’s important to have a UI that has controls (zooming, printing, etc), and that’s where a native embed does that well. PDF.js says that they prefer that you don’t use their viewer as is, instead they want you to re-skin in, and that’s something I’m not interested in doing.
  • I see pdf.js as more of a library to be able to render PDFs in custom ways, rather than just for PDF viewing. I’ve used it in the past to render PDFs with watermarks and in an encrypted way. So it’s a great library, but a little overkill for just embedding PDFs,
  • If I had to embed PDFs in mobile Safari I’d certainly use pdf.js, but we’re only talking about a second click to view. And that’s actually good for mobile bandwidth anyway, it’s almost a lazy load.
1 Like