You are here

class views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer in Views Data Export PDF 7

Same name and namespace in other branches
  1. 7.2 modules/vde_pdf_wkhtmltopdf_mpdf/src/views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer.inc \views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer

A PDF renderer that invokes WK HTML to PDF and then MPDF.

This implementation works reasonably well on small to medium result sets (< 500 pages of data), but runs the risk of timing out on larger result sets unless it's invoked through VDE PDF Background Process.

This implementation uses wkhtmltopdf to efficiently render the content of the PDF, while using mPDF to add HTML headers and footers. This effectively avoids a known issue with wkhtmltopdf and open file limits.

See: https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2093

Hierarchy

Expanded class hierarchy of views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer

1 string reference to 'views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer'
vde_pdf_wkhtmltopdf_mpdf_pdf_export_renderers in modules/vde_pdf_wkhtmltopdf_mpdf/vde_pdf_wkhtmltopdf_mpdf.module
Implements hook_pdf_export_renderers().

File

modules/vde_pdf_wkhtmltopdf_mpdf/src/views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer.inc, line 28
Contains the class for rendering PDFs with WK HTML to PDF AND mPDF.

View source
class views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer extends views_data_export_pdf_renderer_base {

  /**
   * {@inheritDoc}
   */
  public function render_html_file_to_pdf($source_html_file, $target_pdf_file, array $renderer_options) {
    if (!$this
      ->acquire_render_lock($source_html_file)) {
      return FALSE;
    }
    module_load_include('inc', 'vde_pdf_wkhtmltopdf', 'src/views_data_export_pdf_wkhtmltopdf_in_proc_renderer');
    module_load_include('inc', 'vde_pdf_mpdf', 'src/views_data_export_pdf_mpdf_in_proc_renderer');
    try {
      $source_path = $source_html_file->uri;
      $target_path = $target_pdf_file->uri;
      $html_data = file_get_contents($source_path);
      watchdog('vde_pdf_wkhtmltopdf_mpdf', 'Starting to render body of PDF %file with wkhtmltopdf.', [
        '%file' => $source_path,
      ], WATCHDOG_DEBUG);

      // Render the body using wkhtmltopdf
      $pdf_data = $this
        ->render_body_content($html_data, $renderer_options);
      file_put_contents($target_path, $pdf_data);
      watchdog('vde_pdf_wkhtmltopdf_mpdf', 'Finished rendering body of PDF %file with wkhtmltopdf.', [
        '%file' => $source_path,
      ], WATCHDOG_DEBUG);
      if (!empty($renderer_options['header_html']) || !empty($renderer_options['footer_html'])) {
        watchdog('vde_pdf_wkhtmltopdf_mpdf', 'Starting to render headers and footers of PDF %file with mPDF.', [
          '%file' => $source_path,
        ], WATCHDOG_DEBUG);

        // Render the header and/or footer using mPDF
        $header_footer_renderer = $this
          ->render_header_footer($target_path, $renderer_options);
        $header_footer_renderer
          ->Output($target_path, Destination::FILE);
        watchdog('vde_pdf_wkhtmltopdf_mpdf', 'Finished rendering headers and footers of PDF %file with mPDF.', [
          '%file' => $source_path,
        ], WATCHDOG_DEBUG);
      }
      $this
        ->update_target_pdf_file($target_pdf_file);
      return TRUE;
    } catch (MpdfException $ex) {
      self::log_exception($ex);
      throw new views_data_export_pdf_conversion_exception('Failed to post-process PDF. Check site logs for details.', 0, $ex);
    } catch (PdfParserException $ex) {
      self::log_exception($ex);
      throw new views_data_export_pdf_conversion_exception('Failed to post-process PDF. Check site logs for details.', 0, $ex);
    } finally {
      $this
        ->release_render_lock($source_html_file);
    }
  }

  /**
   * {@inheritDoc}
   */
  public function render_html_to_pdf(string $source_html, array $renderer_options) {
    module_load_include('inc', 'vde_pdf_wkhtmltopdf', 'src/views_data_export_pdf_wkhtmltopdf_in_proc_renderer');
    module_load_include('inc', 'vde_pdf_mpdf', 'src/views_data_export_pdf_mpdf_in_proc_renderer');
    try {

      // Render the body using wkhtmltopdf
      $pdf_data = $this
        ->render_body_content($source_html, $renderer_options);
      if (!empty($renderer_options['header_html']) || !empty($renderer_options['footer_html'])) {

        // Render the header and/or footer using mPDF
        $header_footer_renderer = $this
          ->render_header_footer(StreamReader::createByString($pdf_data), $renderer_options);
        $pdf_data = $header_footer_renderer
          ->Output('', Destination::STRING_RETURN);
      }
      return $pdf_data;
    } catch (MpdfException $ex) {
      self::log_exception($ex);
      throw new views_data_export_pdf_conversion_exception('Failed to post-process PDF. Check site logs for details.', 0, $ex);
    } catch (PdfParserException $ex) {
      self::log_exception($ex);
      throw new views_data_export_pdf_conversion_exception('Failed to post-process PDF. Check site logs for details.', 0, $ex);
    }
  }

  /**
   * Builds the options that are passed-in to wkhtmltopdf.
   *
   * Header and footer HTML are automatically removed, since those are not being
   * rendered with wkhtmltopdf.
   *
   * @param array $renderer_options
   *   The options being provided to the renderer to control display of the PDF.
   *   Can include:
   *    - orientation: 'landscape' or 'portrait'.
   *    - page_width: The width of a single portrait-orientation page, in
   *      millimeters.
   *    - page_height: The height of a single portrait-orientation page, in
   *      millimeters.
   *    - header_html: HTML markup for the header of each PDF page.
   *    - footer_html: HTML markup for the footer of each PDF page.
   *
   * @return array
   *   The array of options to pass to the wkhtmltopdf renderer (not the same
   *   as CLI options; this is one step higher-level).
   */
  protected static function build_wkhtmltopdf_renderer_options(array $renderer_options) {

    // Skip headers and footers entirely.
    $wkhtmltopdf_renderer_options = array_filter($renderer_options, function ($key) {
      return !in_array($key, [
        'header_html',
        'footer_html',
      ]);
    }, ARRAY_FILTER_USE_KEY);

    // Must match margins that mPDF would use, so there is space for headers
    // and footers.
    $margin_options = [
      'margin_top' => '18mm',
      'margin_right' => '12mm',
      'margin_bottom' => '18mm',
      'margin_left' => '12mm',
    ];
    return array_merge($wkhtmltopdf_renderer_options, $margin_options);
  }

  /**
   * Builds the options that are passed-in to mPDF.
   *
   * Header and footer HTML are adjusted as if their rendering engine were mPDF.
   *
   * @param array $renderer_options
   *   The options being provided to the renderer to control display of the PDF.
   *   Can include:
   *    - orientation: 'landscape' or 'portrait'.
   *    - page_width: The width of a single portrait-orientation page, in
   *      millimeters.
   *    - page_height: The height of a single portrait-orientation page, in
   *      millimeters.
   *    - header_html: HTML markup for the header of each PDF page.
   *    - footer_html: HTML markup for the footer of each PDF page.
   *
   * @return array
   *   The array of options to pass into the mPDF renderer (not the same
   *   as the options passed into mPDF itself; this is one step higher-level).
   */
  protected static function build_mpdf_options(array $renderer_options) {
    $mpdf_renderer_options = [];
    foreach ($renderer_options as $key => $value) {
      if (in_array($key, [
        'header_html',
        'footer_html',
      ])) {

        // Adjust renderer type from wkhtmltopdf to mMPDF so CSS applies
        // properly.
        $value = preg_replace('/pdf-page-(header|footer)-content--wkhtmltopdf/', 'pdf-page-\\1-content--mpdf', $value);
      }
      $mpdf_renderer_options[$key] = $value;
    }
    return $mpdf_renderer_options;
  }

  /**
   * Invokes the wkhtmltopdf renderer to convert HTML body content to PDF.
   *
   * @param string $source_html
   *   The HTML markup to render.
   * @param array $renderer_options
   *   The options being provided to the renderer to control display of the PDF.
   *   Can include:
   *    - orientation: 'landscape' or 'portrait'.
   *    - page_width: The width of a single portrait-orientation page, in
   *      millimeters.
   *    - page_height: The height of a single portrait-orientation page, in
   *      millimeters.
   *    - header_html: HTML markup for the header of each PDF page.
   *    - footer_html: HTML markup for the footer of each PDF page.
   *
   * @return string
   *   The PDF binary data from wkhtmltopdf.
   *
   * @throws views_data_export_pdf_conversion_exception
   *   If rendering the body content fails.
   */
  protected function render_body_content($source_html, array $renderer_options) {
    $pdf_renderer = new views_data_export_pdf_wkhtmltopdf_in_proc_renderer();
    $wkhtmltopdf_renderer_options = self::build_wkhtmltopdf_renderer_options($renderer_options);

    /** @noinspection PhpUnnecessaryLocalVariableInspection */
    $pdf_data = $pdf_renderer
      ->render_html_to_pdf($source_html, $wkhtmltopdf_renderer_options);
    return $pdf_data;
  }

  /**
   * Adds headers to the given PDF, using mPDF.
   *
   * The PDF data that was produced by wkhtmltopdf is read-in by mPDF, and then
   * imported into a new PDF document in which headers and footers are rendered
   * by mPDF.
   *
   * @param string|resource|StreamReader $body_pdf
   *   Either the path to a PDF file that contains the body content, or a
   *   stream reader for reading the file data.
   * @param array $renderer_options
   *   The options being provided to the renderer to control display of the PDF.
   *   Can include:
   *    - orientation: 'landscape' or 'portrait'.
   *    - page_width: The width of a single portrait-orientation page, in
   *      millimeters.
   *    - page_height: The height of a single portrait-orientation page, in
   *      millimeters.
   *    - header_html: HTML markup for the header of each PDF page.
   *    - footer_html: HTML markup for the footer of each PDF page.
   *
   * @return \Mpdf\Mpdf
   *   The mPDF renderer that contains the PDF content waiting to be written
   *   out.
   *
   * @throws MpdfException
   *   If mMPDF cannot write-out the new PDF content.
   * @throws PdfParserException
   *   If mMPDF cannot read or process the existing PDF file.
   */
  protected function render_header_footer($body_pdf, array $renderer_options) {
    $mpdf_renderer_options = self::build_mpdf_options($renderer_options);
    $mPdf = views_data_export_pdf_mpdf_in_proc_renderer::build_pdf_object_from_options($mpdf_renderer_options);
    $this
      ->apply_header_footer_stylesheet($mpdf_renderer_options, $mPdf);
    $page_count = $mPdf
      ->setSourceFile($body_pdf);
    for ($page_number = 1; $page_number <= $page_count; ++$page_number) {
      $template_id = $mPdf
        ->importPage($page_number);
      if ($page_number > 1) {

        // Advance to the next page before importing this page
        $mPdf
          ->AddPage();
      }

      // Pull the page from the wkhtmltopdf into the new mPDF PDF.
      $mPdf
        ->useTemplate($template_id);
    }
    return $mPdf;
  }

  /**
   * Applies a custom stylesheet to the headers and footers.
   *
   * @param array $mpdf_renderer_options
   *   The options being provided to the renderer to control display of the PDF.
   *   Can include:
   *    - orientation: 'landscape' or 'portrait'.
   *    - page_width: The width of a single portrait-orientation page, in
   *      millimeters.
   *    - page_height: The height of a single portrait-orientation page, in
   *      millimeters.
   *    - header_html: HTML markup for the header of each PDF page.
   *    - footer_html: HTML markup for the footer of each PDF page.
   * @param \Mpdf\Mpdf $mPdf
   *   The mPDF instance to which to write the stylesheet out to.
   *
   * @throws MpdfException
   *   If mMPDF cannot apply the stylesheet.
   */
  protected function apply_header_footer_stylesheet(array $mpdf_renderer_options, Mpdf $mPdf) {
    $default_stylesheet_path = _views_data_export_pdf_get_default_stylesheet_path();
    $stylesheet_path = $mpdf_renderer_options['stylesheet_path'] ?? $default_stylesheet_path;
    $stylesheet_data = file_get_contents($stylesheet_path);
    $mPdf
      ->WriteHTML($stylesheet_data, HTMLParserMode::HEADER_CSS, TRUE, FALSE);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
views_data_export_pdf_error_reporting_trait::log_error protected static function Logs an error message for this module to site logs.
views_data_export_pdf_error_reporting_trait::log_exception protected static function Logs an exception to site logs.
views_data_export_pdf_error_reporting_trait::log_warning protected static function Logs a warning message for this module to site logs.
views_data_export_pdf_renderer::MIME_TYPE_HTML constant The MIME type for the source HTML data.
views_data_export_pdf_renderer::MIME_TYPE_PDF constant The MIME type for the final PDF.
views_data_export_pdf_renderer_base::acquire_render_lock protected function Attempts to acquire a lock for exports from the specified file.
views_data_export_pdf_renderer_base::get_lock_name protected function Gets the lock handle to use for exports from the specified file.
views_data_export_pdf_renderer_base::PDF_GENERATION_LOCK_TIMEOUT constant The maximum amount of time in seconds that a request thread can be converting the same HTML file to PDF before another request can try again.
views_data_export_pdf_renderer_base::release_render_lock protected function Attempts to release a lock for exports on the specified file.
views_data_export_pdf_renderer_base::update_target_pdf_file protected function Updates the MIME type and file size of the output, target PDF file.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::apply_header_footer_stylesheet protected function Applies a custom stylesheet to the headers and footers.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::build_mpdf_options protected static function Builds the options that are passed-in to mPDF.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::build_wkhtmltopdf_renderer_options protected static function Builds the options that are passed-in to wkhtmltopdf.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::render_body_content protected function Invokes the wkhtmltopdf renderer to convert HTML body content to PDF.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::render_header_footer protected function Adds headers to the given PDF, using mPDF.
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::render_html_file_to_pdf public function Converts the contents of the given HTML file into PDF format. Overrides views_data_export_pdf_renderer::render_html_file_to_pdf
views_data_export_pdf_wkhtmltopdf_mpdf_in_proc_renderer::render_html_to_pdf public function Converts the given HTML content into PDF format. Overrides views_data_export_pdf_renderer::render_html_to_pdf