How can I create dynamic pdf files from a page

I am currently manually creating a pdf file of my products but, I would like to display my page as a pdf file for download when I click a button, as they are the same content.

I would like to control how it displays, can I create page-item.pdf.twig template so that if a user visits www.example.com/page-item.pdf it will display a pdf.

if this is not supported by Grav I was thinking of using TCPDF
how can I add this to Grav?

As per the documentation

Grav is a flexible platform however, and can actually serve up any content type you could wish for (xml, rss, json, pdf, etc.), you just have to provide a way to render it appropriately.

If you were to request a route with a .xml extension, for example: /blog.xml, instead of using the regular blog.html.twig template to render it, Grav looks for a template called blog.xml.twig. You would need to ensure that template outputs the appropriate XML structure.

I guess you could have something like

{# my-page.pdf.twig #}
{{ outputPDF(page) }}

And then in some custom plugin with outputPDF($page) Twig function render the PDF using TCPDF :thinking:

Or maybe this outputPDF() even could render your actual my-page.html.twig output. I used TCPDF years ago, but IIRC it can accept HTML and make a PDF. I believe it used to distort the layout then, but maybe worth trying

In your described case, it is enough to optimize your print-stylesheet - as all modern browsers are able to generate PDF files as well.

If you want to generate a completely individual looking PDF-file with specific data from your page (lets say you have built a real estate page and want the user to be able to download an expose for a building in your corporate style) I would recommend jsPDF.
But be aware that coding a PDF-template is as much fun as creating a responsive newsletter-template with tables…

ok, so I am having a go at designing a custom plugin. It works well locally, but I am having issues on the live site.

When I upload the plugin, the whole site doesn’t display, it just shows “this page isn’t working”.

If I edit out code, it seems to be the “class MYPDF extends TCPDF” section; due to if I remove it, the site displays again.

I have installed tcpdf via composer in the plugin root folder. is there something I am missing or doing wrong?

Here is an abbreviated version of the code

<?php
namespace Grav\Plugin;

use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;
use TCPDF;

class MYPDF extends TCPDF {
    public $logoPath;
    public $product;
    public $dxfIconPath;
    public $dxfFilePath;

    public function Header() {
        // Code
    }

    public function Footer() {
        // Code
     }
}

/**
 * Class ProductPDFPlugin
 * @package Grav\Plugin
 */
class ProductPDFPlugin extends Plugin
{
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onPluginsInitialized' => [
                ['onPluginsInitialized', 0]
            ]
        ];
    }

    /**
     * Composer autoload
     *
     * @return ClassLoader
     */
    public function autoload(): ClassLoader
    {
        return require __DIR__ . '/vendor/autoload.php';
    }
    
    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized(): void
    {
        // Don't proceed if we are in the admin plugin
        if ($this->isAdmin()) {
            return;
        }

        // Enable the main events we are interested in
        $this->enable([
            'onPageInitialized' => ['onPageInitialized', 0]
        ]);
    }

    /**
     * Handle the page initialization event
     */
    public function onPageInitialized(): void
    {      
         // Check if the current route is for generating a PDF
         $uri = $this->grav['uri'];
         $path = $uri->path();
         if (preg_match('/^\/products\/.+\.pdf$/', $path)) {
             $this->generatePDF($path);
         }
    }

    /**
     * Generate and display a PDF using TCPDF
     */
    private function generatePDF(string $path): void
    {
        // Extract product name from path
        $productName = basename($path, '.pdf');

        // Remove the .pdf extension to find the original page
        $originalPath = preg_replace('/\.pdf$/', '', $path);

        // Retrieve the original page
        $page = $this->grav['pages']->dispatch($originalPath, true);

        // Get product data from page frontmatter
        $product = $page->header()->product;

        // Create new PDF document
        $pdf = new MYPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

        // Set document information
        $pdf->SetCreator(PDF_CREATOR);
        $pdf->SetAuthor('Your Name');
        $pdf->SetTitle($product['code'] . '[' . $product['name'] . ']');
        $pdf->SetSubject('Product PDF Document');
        $pdf->SetKeywords('TCPDF, PDF, product, guide');

        //HTML PDF CODE

        // Output PDF document
        $pdf->Output( $product['code'] . '[' . $product['name'] . ']'.'.pdf', 'I');

        // Stop Grav from further processing
        exit();
    }
}

Further to this, it seems locally i have got tcpdf installed in the root composer, when removed it acts the same. so my question is how do i make sure it uses TCPDF from the plugin composer?

here is the solution

in your terminal in the plugin root folder

composer required tecnickcom/tcpdf

then in your plugin.php file add the require line

use Composer\Autoload\ClassLoader;
use Grav\Common\Plugin;
require __DIR__ . '/vendor/autoload.php';
use TCPDF;

@dean_007, Yes, it may have solved the error, but it is a bit crude…

I would do the following to better align with PHP PSR-1 codestyle, Composer autoloading and Grav itself:

  • Create a plugin using $ bin/plugin devtools newplugin and name it ProductPDF
    Judging from your code, it seems you’ve used this utility.
  • $ cd user/plugins/product-pdf
  • $ composer require tecnickcom/tcpdf
    Note require instead of required
  • Create file /user/plugins/product-pdf/classes/MyPDF.pdf, with the following content:
    <?php
    namespace Grav\Plugin\ProductPDF;
    
    use TCPDF;
    
    class MyPDF extends TCPDF {
        public $logoPath;
        public $product;
        public $dxfIconPath;
        public $dxfFilePath;
    
        public function Header() {
            // Code
        }
    
        public function Footer() {
            // Code
         }
    }
    
    Note the CamelCase naming convention for both the file and class.
  • Make the following changes in file /user/plugins/product-pdf/product-pdf.php:
    • Add use Grav\Plugin\ProductPDF\MyPDF;
    • Don’t use require __DIR__ . '/vendor/autoload.php';
      public function autoload() will be called by Grav when plugin is initialized.
    • Remove use TCPDF;
    • Remove entire class MYPDF
    • Replace $pdf = new MYPDF(...) with $pdf = new MyPDF(...)

You, sir, are great! Thank you. I have updated it, and it works like a dream. Now I understand a little better how to add extra classes.

Hopefully someone else can learn from this too.