Source of file WkhtmlService.php

Size: 10,991 Bytes - Last Modified: 2023-11-16T22:56:03+01:00

/home/websites/teicee/packagist/site/phpdoc/conf/../vendor/teicee/dpdf-bundle/src/Service/WkhtmlService.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
<?php
namespace TIC\DpdfBundle\Service;

use TIC\DpdfBundle\Base\PDFService as BaseService;

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

/**
 * Service de génération de PDF avec wkhtml(topdf|toimage)
 * (à partir d'un contenu HMTL ou d'une vue Twig).
 */
class WkhtmlService extends BaseService
{
	protected $cmd_bin;
	protected $cmd_opt = array();
	protected $cmd_src;
	protected $cmd_out;
	protected $timeout = 60;

// ------------------------------------------------------------------------------------------ CONFIG

	/**
	 * Initialisation de l'objet Dompdf avec ses options.
	 *
	 * @param   array   $pdf_options    Surcharge d'options pour Dompdf
	 * @return  WkhtmlService
	 */
	public function initEngine(array $pdf_options = []): WkhtmlService
	{
		$opts = array();
		
		// options complétée avec la configuration par défaut (surchargée par parameters)
		$pdf_options+= $this->confs;
		if (isset($pdf_options['debug'])) $this->debug = (bool)$pdf_options['debug'];
		
		// binary: wkhtmltopdf | wkhtmltoimage
		switch (\strtolower($pdf_options['generator'])) {
			case "image":
			case "png":
			case "gd":
				$isPDF = false;
				$this->cmd_bin = $this->paths['wkhtml_image']; break;
			default: // auto, pdf...
				$isPDF = true;
				$this->cmd_bin = $this->paths['wkhtml_pdf']; break;
		}
		
		// options non disponibles avec wkhtmltoimage
		if ($isPDF) {
			//  -s, --page-size <Size>              Set paper size to: A4, Letter, etc.
			if ($pdf_options['paper_size'] && $isPDF)   {
				$opts[] = "--page-size";
				$opts[] = $pdf_options['paper_size'];
			}
			//  -O, --orientation <orientation>     Set orientation to Landscape or Portrait
			if ($pdf_options['orientation']) {
				$opts[] = "--orientation";
				$opts[] = $pdf_options['orientation'];
			}
			//      --print-media-type              Use print media-type instead of screen
			//      --no-print-media-type           Do not use print media-type instead of screen (default)
			if ($pdf_options['media_type'] === 'print') {
				$opts[] = "--print-media-type";
			} else {
				$opts[] = "--no-print-media-type";
			}
			//  -d, --dpi <dpi>                     Change the dpi explicitly (this has no effect on X11 based systems) (default 96)
			//      --image-dpi <integer>           When embedding images scale them down to this dpi (default 600)
			if (! empty($pdf_options['pdi']) && \is_numeric($pdf_options['pdi'])) {
				$opts[] = "--dpi";
				$opts[] = \intval($pdf_options['pdi']);
				$opts[] = "--image-dpi";
				$opts[] = \intval($pdf_options['pdi']);
			}
			//      --no-background                 Do not print background
			if (isset($pdf_options['background']) && empty($pdf_options['background'])) {
				$opts[] = "--no-background";
			}
			//      --disable-smart-shrinking       Disable the intelligent shrinking strategy used by WebKit that makes the pixel/dpi ratio none constant
			if (isset($pdf_options['smart_shrinking']) && empty($pdf_options['smart_shrinking'])) {
				$opts[] = "--disable-smart-shrinking";
			}
			//  -l, --lowquality                    Generates lower quality pdf/ps. Useful to shrink the result document space
			$opts[] = "--lowquality";
			
			// @TODO
			//      --viewport-size <>              Set viewport size if you have custom scrollbars or css attribute overflow to emulate window size
			//  -B, --margin-bottom <unitreal>      Set the page bottom margin
			//  -L, --margin-left <unitreal>        Set the page left margin (default 10mm)
			//  -R, --margin-right <unitreal>       Set the page right margin (default 10mm)
			//  -T, --margin-top <unitreal>         Set the page top margin
			//      --outline                       Put an outline into the pdf (default)
			//      --no-outline                    Do not put an outline into the pdf
			//      --outline-depth <level>         Set the depth of the outline (default 4)
			//      --footer-html <url>             Adds a html footer
			//      --header-html <url>             Adds a html header
			//      --replace <name> <value>        Replace [name] with value in header and footer (repeatable)
			//      --image-quality <integer>       When jpeg compressing images use this quality (default 94)
		}
		
		// options spécifiques à wkhtmltoimage
		if (! $isPDF) {
			//  -f, --format <format>               Output file format
			$opts[] = "--format";
			$opts[] = "PNG";
			
			// @TODO
			//      --height <int>                  Set screen height (default is calculated from page content) (default 0)
			//      --crop-h <int>                  Set height for cropping
			//      --crop-w <int>                  Set width for cropping
			//      --crop-x <int>                  Set x coordinate for cropping
			//      --crop-y <int>                  Set y coordinate for cropping
			//      --quality <int>                 Output image quality (between 0 and 100)
		}
		
		//      --cache-dir <path>              Web cache directory
		if (! empty($this->paths['path_tmpdir'])) {
			if (! \is_dir($this->paths['path_tmpdir']."/wkweb")) @\mkdir($this->paths['path_tmpdir']."/wkweb", 0750, true);
			$opts[] = "--cache-dir";
			$opts[] = $this->paths['path_tmpdir']."/wkweb";
		}
		//      --disable-local-file-access     Do not allowed conversion of a local file to read in other local files, unless explicitly allowed with --allow
			$opts[] = "--disable-local-file-access";
		//      --allow <path>                  Allow the file or files from the specified folder to be loaded (repeatable)
		if (! empty($this->paths['path_source'])) {
			$opts[] = "--allow";
			$opts[] = $this->paths['path_source'];
		}
		if (! empty($this->paths['root_dir'])) {
			$opts[] = "--allow";
			$opts[] = $this->paths['root_dir'];
		}
		
		//  -n, --disable-javascript            Do not allow web pages to run javascript
		if (isset($pdf_options['javascript']) && empty($pdf_options['javascript'])) {
			$opts[] = "--disable-javascript";
		}
		//      --load-error-handling <handler> Specify how to handle pages that fail to load: abort, ignore or skip (default abort)
			$opts[] = "--load-error-handling";
			$opts[] = "ignore";
		//      --load-media-error-handling <handler> Specify how to handle media files that fail to load: abort, ignore or skip (default ignore)
			$opts[] = "--load-media-error-handling";
			$opts[] = "ignore";
		//  -q, --quiet                         Be less verbose
			$opts[] = "--quiet";
		
		// @TODO
		//      --zoom <float>                  Use this zoom factor (default 1)
		//      --user-style-sheet <url>        Specify a user style sheet, to load with
		//      --javascript-delay <msec>       Wait some milliseconds for javascript finish (default 200)
		
		if ($this->debug) dump($opts);
		
		$this->cmd_opt = $opts;
		return $this;
	}

// ------------------------------------------------------------------------------------------ LOADER

	/**
	 * Initialisation de l'objet Dompdf à partir d'un fichier HTML.
	 *
	 * @param   string  $html_file      Chemin du fichier HTML à charger
	 * @param   array   $pdf_options    Surcharge d'options pour Dompdf
	 * @return  WkhtmlService
	 */
	public function loadHtmlFile(string $html_file, array $pdf_options = []): WkhtmlService
	{
		$this->initEngine($pdf_options);
		$this->cmd_src = $this->fixHtmlFile($html_file);
		$this->cmd_out = $this->createTempFile("/wktmp", "out");
		return $this;
	}

	/**
	 * Initialisation de l'objet Dompdf à partir d'un contenu HTML.
	 *
	 * @param   string  $html_data      Contenu du document HTML à charger
	 * @param   array   $pdf_options    Surcharge d'options pour Dompdf
	 * @return  WkhtmlService
	 */
	public function loadHtmlData(string $html_data, array $pdf_options = []): WkhtmlService
	{
		$html_file = $this->createTempFile("/wktmp", "html", $html_data);
		return $this->loadHtmlFile($html_file, $pdf_options);
	}

// ------------------------------------------------------------------------------------------ RENDER

	/**
	 * Returns the PDF data as a string.
	 *
	 * @param   bool    $nocompress     Désactivation de la compression PDF
	 * @return  string                  Données binaires du document PDF
	 */
	public function renderData(bool $nocompress = false): string
	{
		list($status, $stdout, $stderr) = $this->executeCommand($nocompress);
		$this->checkCommandOutput($status, $stdout, $stderr, $this->cmd_out);
		return \file_get_contents($this->cmd_out);
	}

// ------------------------------------------------------------------------------------------ PROCESS

	/**
	 * Exécution de la commande de génération wkhtmlto(pdf|image).
	 *
	 * @param   bool    $nocompress     Désactivation de la compression PDF
	 * @return  array                   Retour de la commande [status, stdout, stderr]
	 */
	protected function executeCommand(bool $nocompress = false): array
	{
		if (! isset($this->cmd_bin))
			throw new \LogicException("Initialisation error: wkhtml command is not defined!");
		
		$cmd_args = array();
		if ($nocompress) $cmd_args[] = "--no-pdf-compression";
		$cmd_args[] = $this->cmd_src;
		$cmd_args[] = $this->cmd_out;

		$command = \array_merge([ $this->cmd_bin ], $this->cmd_opt, $cmd_args);
		$process = new Process($command);
		if ($this->debug) dump($command, $process->getCommandLine());
		
		if (isset($this->timeout)) $process->setTimeout($this->timeout);
		$process->run();
		
#		if (! $process->isSuccessful()) throw new ProcessFailedException($process);
		return [
			$process->getExitCode(),
			$process->getOutput(),
			$process->getErrorOutput(),
		];
	}

// ------------------------------------------------------------------------------------------ CHECKS

	/**
	 * Vérification du résultat de la commande de génération wkhtmlto(pdf|image).
	 *
	 * @param   int     $status         Command exit status code
	 * @param   string  $stdout         Command stdout content
	 * @param   string  $stderr         Command stderr content
	 * @param   string  $output         Command output fila path
	 * @throws  RuntimeException        Message d'erreur sur échec de la génération
	 * @return  void
	 */
	protected function checkCommandOutput(int $status, string $stdout, string $stderr, string $output)
	{
		if ($this->debug) dump($status, $stdout, $stderr, $output);
		$dbg = "\n*** stderr:\n%s\n*** stdout:\n%s\n";
#		if (0 !== $status && '' !== $stderr)
#			throw new \RuntimeException(\sprintf("Process failed: wkhtml error (%d)".$dbg, $status, $stderr, $stdout));
		if (! \file_exists($output))
			throw new \RuntimeException(\sprintf("Process failed: output not exists (%s)".$dbg, $output, $stderr, $stdout));
		if (! \filesize($output))
			throw new \RuntimeException(\sprintf("Process failed: output is empty (%s)".$dbg, $output, $stderr, $stdout));
	}

	protected function getFileType(string $type = "PDF"): ?array
	{
		if (isset($this->cmd_bin) && (\strpos($this->cmd_bin, "image") !== false)) $type = "PNG";
		return parent::getFileType($type);
	}

}