From 783cc4cbdd195318c235b60468ce24e6990118a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bl=C3=A1ha?= Date: Sat, 8 Feb 2025 00:46:11 +0100 Subject: [PATCH] Updates for laravel 10 --- README.md | 146 +++++++++++++++++++++++++++------ composer.json | 6 +- config/config.php | 6 ++ src/LaraTeX.php | 115 +++++++++++++++++++------- src/LaraTeXServiceProvider.php | 6 ++ src/LaratexCollection.php | 2 - src/LaratexException.php | 24 ++++++ src/LatexEscaper.php | 24 ++++++ src/RawTex.php | 1 - 9 files changed, 270 insertions(+), 60 deletions(-) create mode 100644 src/LatexEscaper.php diff --git a/README.md b/README.md index dc492dc..5b5a7cd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Laratex -

LaraTeX

+

LaraTeX

A laravel package to generate PDFs using LaTeX @@ -29,9 +29,6 @@

Table of Contents
    -
  1. - Note -
  2. Getting Started
-## NOTE - -This package was tested in two different environments while using the package for those two special processes. -If you experience any issues in all the time you are using it please open an issue so I can make this package better with every update :) - ## Getting Started ### Important information about your environment @@ -83,14 +78,14 @@ Windows is really good with paths using both forward & backslashes but just keep ### Prerequisites You need to have `texlive-full` installed on your server. This program has tex packages and language libraries which help you generate documents. -Note : You can also choose to install `textlive` which is the lighter version of the package. +Note: You can also choose to install `textlive` which is the lighter version of the package. The difference is: - When you install `textlive` and want to use any additional tex package, you need to install it manually. - `texlive-full` comes with these extra packages. As a result it may take up some additional space on your server (to store the package library files). -If you are choosing a hosting provider that doesn't allow you to install applications yourself please make sure that pdflatex is installed or ask if it can get installed. Also make sure that you have SSH access to the server as you might need it to find out in which path your pdflatex installation is sitting. +If you are choosing a hosting provider that doesn't allow you to install applications yourself please make sure that pdflatex, xelatex or lualatex is installed or ask if it can get installed. Also make sure that you have SSH access to the server as you might need it to find out in which path your pdflatex installation is sitting. ### Installation @@ -117,11 +112,24 @@ On Unix systems you can find out which bin path to use by running the command `w If you are running this package with on a windows system please check this in cmd.exe before. There you should find out if running the command `pdflatex` works in cmd or if you need to provide the absolute path to your pdflatex application. +If you cannot just run pdflatex you might have to add the path to your pdflatex compiler in your PATH system environment variables. + +**bibTexPath** +If your system doesn't allow to just run the command line command "bibtex" you may specify the correct one. +On Unix systems you can find out which bin path to use by running the command `which bibtex` + +If you are running this package with on a windows system please check this in cmd.exe before. +There you should find out if running the command `bibtex` works in cmd or if you need to provide the absolute path to your bibtex application. +If you cannot just run bibtex you might have to add the path to your bibtex compiler in your PATH system environment variables. **tempPath** This specifies the folder where temporary files are saved while rendering a tex file into a PDF file. It is important that you always **start your path without a slash** and **end your path with a slash** (e.g. app/pdf/) +**teardown** +As seen in the section Garbage Collection this package deletes all temp files (log, aux etc.) created while generating the PDF file. When debugging successfully generated PDF files it can be useful to check the generated tex file. +Set this setting to `false` if you don't want LaraTeX to delete those files after generating the PDF. + ## Usage ### Dry Run @@ -224,12 +232,77 @@ You can see how we have easily used blade directives for `{{ $name }}` to show a For more complex LaTeX files where you may need to use blade directives like `{{ $var }}` inside of a LaTeX command which already uses curly brackets (e.g. `\textbf{}`) you can always use Laravels `@php @endphp` method or plain PHP like `` or `` (Example: `\textbf{}`). +As an addition there is also a `@latex()` Blade directive mentioned in the next chapter. + **Important note when using html characters** When using the `{{ }}` statement in a blade template, Laravel's blade engine always sends data through the PHP function `htmlspecialchars()` first. This will convert characters like `&` to `&` and `<` to `<` to just mention a few. pdflatex doesn't like those converted string and will throw an error like `Misplaced alignment tab character &.`. To fix this issue you have to use the `{!! !!}` statement so that unescaped text is written to your tex template. +### Using the Blade directive + +Since LaTeX has its own syntax it is not advised to use the standard blade syntax `{{ $variable }}` or `{!! $variable !!}`. Instead you can use `@latex($variable)` in your blade templates instead, which handles the suitable escaping of reserved LaTeX characters. + +Create a view file inside `resources/views/latex/tex.blade.php` +You are of course free to create your view files wherever you want inside of your resources folder. +Just make sure to define the view to use correctly later. + +```php +\documentclass[a4paper,9pt,landscape]{article} +\usepackage{adjustbox} +\usepackage[english]{babel} +\usepackage[scaled=.92]{helvet} +\usepackage{fancyhdr} +\usepackage[svgnames,table]{xcolor} +\usepackage[a4paper,inner=1.5cm,outer=1.5cm,top=1cm,bottom=1cm,bindingoffset=0cm]{geometry} +\usepackage{blindtext} +\geometry{textwidth=\paperwidth, textheight=\paperheight, noheadfoot, nomarginpar} + +\renewcommand{\familydefault}{\sfdefault} + +\pagestyle{fancy} +\fancyhead{} +\renewcommand{\headrulewidth}{0pt} +\fancyfoot{} +\fancyfoot[LE,RO]{\thepage} + +\fancyfoot[C]{\fontsize{8pt}{8pt}\selectfont The above document is auto-generated.} +\renewcommand{\footrulewidth}{0.2pt} + +\begin{document} + + \section*{\centering{LaraTeX Demo Document}} + + \begin{center} + \item[Name :] @latex($Name) + \item[Date of Birth :] @latex($Dob) + \end{center} + + \blindtext + + \begin{table}[ht] + \centering + \renewcommand{\arraystretch}{2} + \begin{tabular}{|c|l|} + \hline + \rowcolor[HTML]{E3E3E3} + \textbf{ID} & \textbf{Language} \\ + \hline\renewcommand{\arraystretch}{1.5} + + @foreach($languages as $key => $language) + @latex($key) & @latex($language) \\ \hline + @endforeach + + \end{tabular} + \caption{Language Summary} + \end{table} + +\end{document} +``` + +You can see how we have easily used the `@latex()` Blade directive to print variables. + ### Using graphics inside of your LaTeX files Where exactly pdflatex looks for graphics included inside of a .tex file I am not really sure. @@ -403,6 +476,42 @@ return (new LaraTeX($tex))->with([ ])->download('test.pdf'); ``` +### Compile multiple times + +There are a few cases in which it is necessary to compile twice. If you are using a table of contents (TOC) for example, or if you use the package `lastpage` to get a better pagination (`Page n of n`) as another example. + +LaraTeX compiles once as a default. If you need to compile twice (or - for whatever reason more than twice) you can use the method `compileAmount()` to achieve this. + +```php +return (new LaraTeX('latex.tex'))->with([ + 'Name' => 'John Doe', + 'Dob' => '01/01/1990', + 'SpecialCharacters' => '$ (a < b) $', + 'languages' => [ + 'English', + 'Spanish', + 'Italian' + ] +])->compileAmount(2)->download('test.pdf'); +``` + +### Compile using bibtex + +If you want to use `bibtex`, please make sure that you have the `bibTexPath` property set correctly inside your `laratex.php` config file. + +```php +return (new LaraTeX('latex.tex'))->with([ + 'Name' => 'John Doe', + 'Dob' => '01/01/1990', + 'SpecialCharacters' => '$ (a < b) $', + 'languages' => [ + 'English', + 'Spanish', + 'Italian' + ] +])->renderBibtex()->compileAmount(3)->download('test.pdf'); +``` + ### Bulk download in a ZIP archive You want to export multiple PDFs inside of a ZIP-Archive? This package has that functionality ready for you. This gives a great flexibility for you. However, make sure you are not passing too many PDFs together, as it is going to consume a good amount of server memory to export those together. @@ -529,9 +638,9 @@ class LaratexPdfWasGeneratedConfirmation */ public function handle(LaratexPdfWasGenerated$event) { - // Path of pdf in case in was saved + // Path of PDF in case it was saved // OR - // Downloaded name of pdf file in case it was downloaded in response directly + // Downloaded name of PDF file in case it was downloaded in response directly $pdf = $event->pdf; // download OR savepdf @@ -575,14 +684,3 @@ Please see [CHANGELOG](CHANGELOG.md) for more information about any major change ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. - -[contributors-shield]: https://img.shields.io/github/contributors/webstasolutions/laratex.svg?style=for-the-badge -[contributors-url]: https://github.com/webstasolutions/laratex/graphs/contributors -[forks-shield]: https://img.shields.io/github/forks/webstasolutions/laratex.svg?style=for-the-badge -[forks-url]: https://github.com/webstasolutions/laratex/network/members -[stars-shield]: https://img.shields.io/github/stars/webstasolutions/laratex.svg?style=for-the-badge -[stars-url]: https://github.com/webstasolutions/laratex/stargazers -[issues-shield]: https://img.shields.io/github/issues/webstasolutions/laratex.svg?style=for-the-badge -[issues-url]: https://github.com/webstasolutions/laratex/issues -[license-shield]: https://img.shields.io/github/license/webstasolutions/laratex.svg?style=for-the-badge -[license-url]: https://github.com/webstasolutions/laratex/blob/master/LICENSE.md diff --git a/composer.json b/composer.json index 5e7fe90..21a5f32 100644 --- a/composer.json +++ b/composer.json @@ -20,9 +20,9 @@ ], "require": { "php": "^8.0", - "illuminate/support": "5.4.x|5.5.X|5.6.X|5.7.X|5.8.X|^6.0|^7.0|^8.0|^9.3", - "illuminate/filesystem": "5.4.x|5.5.X|5.6.X|5.7.X|5.8.X|^6.0|^7.0|^8.0|^9.3", - "symfony/process": "^2.0|^3.0|^4.0|^5.0|^6.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/filesystem": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "symfony/process": "^3.0|^4.0|^5.0|^6.0|^7.0", "ext-zip": "*" }, "autoload": { diff --git a/config/config.php b/config/config.php index 1d52bb8..e2c4b92 100644 --- a/config/config.php +++ b/config/config.php @@ -4,6 +4,12 @@ // bin path to your pdflatex installation | use 'which pdflatex' on a linux system to find out which is the path to your pdflatex installation 'binPath' => 'pdflatex', + // bin path to your bibtex installation | use 'which bibtex' on a linux system to find out which is the path to your bibtex installation + 'bibTexPath' => 'bibtex', + // Folder in your storage folder where you would like to store the temp files created by LaraTeX 'tempPath' => 'app/', + + // boolean to define if log, aux and tex files should be deleted after generating PDF + 'teardown' => true, ]; diff --git a/src/LaraTeX.php b/src/LaraTeX.php index 86aa6b9..a7e4f04 100644 --- a/src/LaraTeX.php +++ b/src/LaraTeX.php @@ -3,8 +3,8 @@ namespace Websta\LaraTeX; use Websta\LaraTeX\LaratexException; -use Websta\LaraTeX\LaratexPdfWasGenerated; use Websta\LaraTeX\LaratexPdfFailed; +use Websta\LaraTeX\LaratexPdfWasGenerated; use Websta\LaraTeX\ViewNotFoundException; use Symfony\Component\Process\Process; use Illuminate\Support\Str; @@ -25,7 +25,7 @@ class LaraTeX * * @var array */ - private array $data; + private array $data = []; /** * Rendered tex file @@ -58,11 +58,33 @@ class LaraTeX */ protected string $binPath; + /** + * @var string + */ + protected string $bibTexPath; + /** * @var string */ protected string $tempPath; + /** + * Number of times to compile the TeX file. (for TOC generation for example) + * + * @var integer + */ + private $compileAmount = 1; + + /** + * Should we run BibTeX before generating? + */ + public bool $generateBibtex = false; + + /** + * @var bool + */ + protected bool $doTeardown = true; + /** * Construct the instance * @@ -73,6 +95,8 @@ public function __construct(string $stubPath = null, mixed $metadata = null) { $this->binPath = config('laratex.binPath'); $this->tempPath = config('laratex.tempPath'); + $this->bibTexPath = config('laratex.bibTexPath'); + $this->doTeardown = config('laratex.teardown'); if ($stubPath instanceof RawTex) { $this->isRaw = true; $this->renderedTex = $stubPath->getTex(); @@ -82,6 +106,25 @@ public function __construct(string $stubPath = null, mixed $metadata = null) $this->metadata = $metadata; } + /** + * Set the number of times to compile + * + * @param integer $compileAmount + * + * @return LaraTeX + */ + public function compileAmount(int $compileAmount): self + { + $this->compileAmount = $compileAmount; + return $this; + } + + public function renderBibtex(): self + { + $this->generateBibtex = true; + return $this; + } + /** * Set name inside zip file * @@ -127,14 +170,6 @@ public function with(array $data): LaraTeX public function dryRun(): \Symfony\Component\HttpFoundation\BinaryFileResponse { $this->isRaw = true; - $process = new Process(["which", "pdflatex"]); - $process->run(); - - // if (!$process->isSuccessful()) { - // - // throw new LaratexException($process->getOutput()); - // } - $this->renderedTex = File::get(dirname(__FILE__) . '/dryrun.tex'); return $this->download('dryrun.pdf'); } @@ -284,14 +319,25 @@ private function generate(): string $program = $this->binPath ? $this->binPath : 'pdflatex'; $cmd = [$program, '-shell-escape', '-output-directory', $tmpDir, $tmpfname]; - $process = new Process($cmd); - $process->run(); - if (!$process->isSuccessful()) { - LaratexPdfFailed::dispatch($fileName, 'download', $this->metadata); - $this->parseError($tmpfname, $process); + for ($i = 1; $i <= $this->compileAmount; $i++) { + + // BibTeX must be run after the first generation of the LaTeX file. + if ($i === 2 && $this->generateBibtex) { + $bibtex = new Process([$this->bibTexPath, basename($tmpfname)], $tmpDir); + $bibtex->run(); + } + + $process = new Process($cmd); + $process->run(); + if (!$process->isSuccessful()) { + LaratexPdfFailed::dispatch($fileName, 'download', $this->metadata); + $this->parseError($tmpfname, $process); + } } - $this->teardown($tmpfname); + if ($this->doTeardown) { + $this->teardown($tmpfname); + } register_shutdown_function(function () use ($tmpfname) { if (File::exists($tmpfname . '.pdf')) { @@ -307,23 +353,22 @@ private function generate(): string * * @param string $tmpfname * - * @return void + * @return \Websta\LaraTeX\LaraTeX */ - private function teardown(string $tmpfname): void + private function teardown(string $tmpfname): self { if (File::exists($tmpfname)) { File::delete($tmpfname); } - if (File::exists($tmpfname . '.aux')) { - File::delete($tmpfname . '.aux'); - } - if (File::exists($tmpfname . '.log')) { - File::delete($tmpfname . '.log'); - } - if (File::exists($tmpfname . '.out')) { - File::delete($tmpfname . '.out'); + + $extensions = ['aux', 'log', 'out', 'bbl', 'blg', 'toc', 'tex']; + foreach ($extensions as $extension) { + if (File::exists($tmpfname . '.' . $extension)) { + File::delete($tmpfname . '.' . $extension); + } } + return $this; } /** @@ -335,15 +380,25 @@ private function teardown(string $tmpfname): void */ private function parseError(string $tmpfname, $process): void { - - $logFile = $tmpfname . 'log'; + $logFile = $tmpfname . '.log'; + $texFileNoExtension = $tmpfname; + $texFileExtension = $tmpfname . '.tex'; if (!File::exists($logFile)) { - throw new LaratexException($process->getOutput()); + throw new LaratexException($process->getErrorOutput() . ' - ' . $process->getOutput()); + } + + if (File::exists($texFileNoExtension)) { + $texFileContent = File::get($texFileNoExtension); + } elseif (File::exists($texFileExtension)) { + $texFileContent = File::get($texFileExtension); + } else { + $texFileContent = 'Tex file not found'; } $error = File::get($logFile); - throw new LaratexException($error); + + throw LaratexException::detailed($error, $texFileContent); } /** diff --git a/src/LaraTeXServiceProvider.php b/src/LaraTeXServiceProvider.php index a5c6ebb..e14672b 100644 --- a/src/LaraTeXServiceProvider.php +++ b/src/LaraTeXServiceProvider.php @@ -2,6 +2,7 @@ namespace Websta\LaraTeX; +use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class LaraTeXServiceProvider extends ServiceProvider @@ -16,6 +17,11 @@ public function boot() __DIR__.'/../config/config.php' => config_path('laratex.php'), ], 'config'); } + + Blade::directive('latex', function ($exp) { + $path = LatexEscaper::class; + return ""; + }); } /** diff --git a/src/LaratexCollection.php b/src/LaratexCollection.php index 400a70a..f612b93 100644 --- a/src/LaratexCollection.php +++ b/src/LaratexCollection.php @@ -142,11 +142,9 @@ private function generate(): LaratexCollection */ private function moveToCollectionDir(): LaratexCollection { - $this->makeCollectionDir(); foreach ($this->collection as $latex) { - $name = $latex->getName() ? $latex->getName() : Str::random(4) . '.pdf'; $pdf = $this->collectionDir . DIRECTORY_SEPARATOR . $name; $latex->savePdf($pdf); diff --git a/src/LaratexException.php b/src/LaratexException.php index c3e3b86..4282a98 100644 --- a/src/LaratexException.php +++ b/src/LaratexException.php @@ -4,5 +4,29 @@ class LaratexException extends \Exception { + /** + * @var string + */ + protected string $texcontent; + /** + * @param string $message + * @param string|null $texcontent + * @return static + */ + public static function detailed(string $message, ?string $texcontent = null): self + { + $instance = new static($message); + $instance->texcontent = $texcontent; + + return $instance; + } + + public function context(): array + { + return [ + 'user_id' => auth()?->user()?->id ?? '', + 'tex_content' => $this->texcontent, + ]; + } } diff --git a/src/LatexEscaper.php b/src/LatexEscaper.php new file mode 100644 index 0000000..35e90e4 --- /dev/null +++ b/src/LatexEscaper.php @@ -0,0 +1,24 @@ +tex = $tex; }