ismaelw/laratex

一个用于在 Laravel 中使用 LaTeX 创建 PDF 的包

支持包维护!
ismaelw

v1.2.3 2024-03-20 15:46 UTC

This package is auto-updated.

Last update: 2024-09-20 16:47:05 UTC


README

Contributors Forks Stargazers Issues MIT License


Laratex

LaraTeX

一个用于生成 PDF 的 Laravel 包(使用 LaTeX)

· 报告错误 · 请求功能

为了更好的可视化,您可以在这里找到一个小型 演示HTML 到 LaTeX 转换器

目录
  1. 入门
  2. 使用
  3. HTML 到 LaTeX 转换 BETA
  4. 垃圾回收
  5. 错误处理
  6. 贡献
  7. 致谢
  8. 变更日志
  9. 许可证

入门

有关您的环境的重要信息

本包在 Unix (FreeBSD) 服务器上开发和测试,并在安装了 pdflatex 的 Windows 机器上成功测试。请始终确保正确编写路径 :)

此包使用了 storage_path() 函数。在 Windows 上,可能使用反斜杠写入绝对路径。Windows 在使用正斜杠和反斜杠的路径方面都非常出色,但如果在 Windows 上出现问题,请记住这一点。

先决条件

您需要在您的服务器上安装 texlive-full。此程序包含 TeX 包和语言库,有助于您生成文档。注意:您也可以选择安装 textlive,这是该包的较轻版本。

区别在于

  • 当您安装 textlive 并想使用任何额外的 TeX 包时,您需要手动安装它。
  • texlive-full 随带这些额外包。因此,它可能在您的服务器上占用一些额外的空间(用于存储包库文件)。

如果您选择的托管提供商不允许您自行安装应用程序,请确保已安装 pdflatex、xelatex 或 lualatex,或者询问是否可以安装。此外,请确保您有服务器的 SSH 访问权限,因为您可能需要它来找出 pdflatex 安装所在的路径。

安装

您可以使用 composer 安装此包

composer require ismaelw/laratex

配置

要加载配置文件,请使用 php artisan 运行以下命令

php artisan vendor:publish --tag=config

之后,请确保配置您的 LaraTeX 安装。在您的 LaraTeX 配置文件 \config\laratex.php 中,您可以配置两个设置

binPath 如果您的系统不允许直接运行命令行命令 "pdflatex",您可能需要指定正确的命令。在 Unix 系统上,您可以通过运行命令 which pdflatex 来找出要使用的 bin 路径

如果您在 Windows 系统上运行此包,请在 cmd.exe 中检查这一点。在那里,您应该找出是否可以通过 cmd 运行 pdflatex 命令,或者是否需要提供您的 pdflatex 应用程序的绝对路径。如果您不能直接运行 pdflatex,您可能需要将 pdflatex 编译器的路径添加到您的系统环境变量中的 PATH。

bibTexPath 如果您的系统不允许直接运行命令行命令 "bibtex",您可以指定正确的路径。在 Unix 系统上,您可以通过运行命令 which bibtex 来找出要使用的 bin 路径。

如果您在 Windows 系统上运行此包,请在 cmd.exe 中检查这一点。在那里,您应该找出是否可以通过 cmd 运行 bibtex 命令,或者是否需要提供您的 bibtex 应用程序的绝对路径。如果您不能直接运行 bibtex,您可能需要将 bibtex 编译器的路径添加到您的系统环境变量中的 PATH。

tempPath 这指定了在将 TeX 文件渲染为 PDF 文件时保存临时文件的文件夹。您必须始终以不带斜杠的路径开始,并以斜杠结束(例如 app/pdf/)。

teardown 如垃圾回收章节中所示,此包将删除在生成 PDF 文件时创建的所有临时文件(日志、aux 等)。在调试成功生成的 PDF 文件时,检查生成的 TeX 文件可能很有用。如果您不希望 LaraTeX 在生成 PDF 后删除这些文件,请将此设置设置为 false

使用

干运行

在直接使用之前,请确保您的服务器上已正确安装了所需程序。此包附带 dryrun 方法。如果服务器上一切设置正确,它将自动生成一个名为 dryrun.pdf 的文件。如果不正确,请再次检查上述 binPath 的配置。

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Ismaelw\LaraTeX\LaraTeX;

class TestController extends Controller
{
    /**
     * Download PDF generated from latex
     *
     * @return Illuminate\Http\Response
     */
    public function download(){
        return (new LaraTeX)->dryRun();
    }
}

Dryrun 如果 pdflatex 设置正确,将下载一个美观的干净测试 PDF。

dryrun.pdf sample

使用此包,您有多种选择。您可以直接渲染 PDF 文件并下载它,将其保存到某处,仅获取 TeX 内容,或批量下载包含多个生成 PDF 文件的 ZIP 文件。

准备包含 LaTeX 内容的 Laravel 视图

resources/views/latex/tex.blade.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 :] {{ $Name }}
        \item[Date of Birth :] {{ $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)
                {{ $key }} & {{ $language }} \\ \hline
             @endforeach
             
        \end{tabular}
        \caption{Language Summary}
    \end{table}
    
    \begin{center}
        {!! $SpecialCharacters !!}
    \end{center}

\end{document}

您可以看到我们如何轻松使用 blade 指令 {{ $name }} 来显示一个名称,或使用 @foreach 来在表格中显示语言以动态生成内容。

对于可能需要在 LaTeX 命令中使用 blade 指令(如 {{ $var }})的更复杂的 LaTeX 文件,这些命令已经使用了花括号(例如 \textbf{}),您始终可以使用 Laravel 的 @php @endphp 方法或纯 PHP(如 <?php echo $var; ?><?= $var ?>)来处理保留的 LaTeX 字符的适当转义(例如:\textbf{<?= $var ?>})。

作为补充,下一章还提到了 @latex() Blade 指令。

使用 HTML 字符时的重要注意事项

当在 blade 模板中使用 {{ }} 语句时,Laravel 的 blade 引擎始终会首先通过 PHP 函数 htmlspecialchars() 传递数据。这将像将 & 转换为 &amp;、将 < 转换为 &lt; 等等一样,转换字符。pdflatex 不喜欢这些转换后的字符串,并会抛出类似 Misplaced alignment tab character &. 的错误。

要解决这个问题,您必须使用 {!! !!} 语句,以便将未转义的文本写入您的 TeX 模板。

使用 Blade 指令

由于 LaTeX 有其自己的语法,因此不建议使用标准的 blade 语法 {{ $variable }}{!! $variable !!}。相反,您可以在 blade 模板中使用 @latex($variable),它将处理保留的 LaTeX 字符的适当转义。

resources/views/latex/tex.blade.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}

您可以看到我们如何轻松地使用@latex() Blade指令来打印变量。

在 LaTeX 文件中使用图形

关于pdflatex在何处查找包含在.tex文件中的图形,我并不十分确定。对我最有帮助的是始终给出图形的绝对路径,例如:\includegraphics[scale=0.06]{/absolute/path/to/www/storage/graphics/file.pdf}。如果您有更好的想法,请帮助我并分享您的知识:)

下载PDF文件

download(string $fileName = null)
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Ismaelw\LaraTeX\LaraTeX;

class TestController extends Controller
{
    /**
     * Download PDF generated from LaTex
     *
     * @return Illuminate\Http\Response
     */
    public function download(){

        return (new LaraTeX('latex.tex'))->with([
            'Name' => 'John Doe',
            'Dob' => '01/01/1990',
            'SpecialCharacters' => '$ (a < b) $',
            'languages' => [
                'English',
                'Spanish',
                'Italian'
            ]
        ])->download('test.pdf');
    }
}

如果您将blade文件命名为不同的名称或将其放在另一个文件夹中,请确保正确设置blade文件:return (new LaraTeX('folder.file'))

保存 PDF 文件

要保存PDF文件,请使用savePdf方法。

savePdf(string $location)
(new LaraTeX('latex.tex'))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->savePdf(storage_path('app/export/test.pdf'));

请确保目标文件夹位于您的storage文件夹内部。

返回 PDF 内容

要仅获取原始或base64的PDF内容,请使用content方法。

content(string $type = 'raw')

默认为raw

(new LaraTeX('latex.tex'))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->content();

或者使用base64

(new LaraTeX('latex.tex'))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->content('base64');

以内联方式返回 PDF

要仅获取内联PDF,请使用inline方法。

inline(string $fileName = null)
(new LaraTeX('latex.tex'))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->inline('filename.pdf');

这将返回一个内联文档流,显示为filename.pdf

返回 TeX 数据

render()
$tex = new LaraTeX('latex.tex'))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->render();

使用原始 TeX

如果您不想使用tex文件作为视图,但已经有tex内容,或者正在使用其他库生成tex内容,可以使用RawTex类而不是传递视图路径

use Ismaelw\LaraTeX\LaraTeX;
use Ismaelw\LaraTeX\RawTex;

...

$tex = new RawTex('your_raw_tex_content_string.....');

return (new LaraTeX($tex))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->download('test.pdf');

多次编译

有一些情况下需要编译两次。例如,如果您使用目录(TOC)或者使用lastpage包以获得更好的分页(例如“第n页,共n页”)。

LaraTeX默认只编译一次。如果您需要编译两次(或者出于任何原因需要编译多次),可以使用compileAmount()方法来实现。

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');

使用 bibtex 编译

如果您想使用bibtex,请确保您已正确设置laratex.php配置文件中的bibTexPath属性。

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');

批量下载 ZIP 归档

您想在一个ZIP存档中导出多个PDF文件?这个包已经为您准备好了这项功能。这为您提供了极大的灵活性。但是,请确保您没有同时传递太多的PDF文件,因为将它们一起导出会消耗大量的服务器内存。

$latexCollection = (new LaratexCollection());
$users = User::limit(10)->get();
foreach ($users as $user) {
    $pdfName = $user->first_name.'-'.$user->last_name.'-'.$user->id.'.pdf';

    // Create LaraTeX instance
    $laratex= (new LaraTeX('latex.report'))->with([
        'user' => $user
    ])->setName($pdfName);

    // Add it to latex collection
    $latexCollection->add($laratex);
}

// Download the zip
return $latexCollection->downloadZip('Users.zip');

// OR you can also save it
$latexCollection->saveZip(storage_path('app/pdf/zips/Users.zip'));

HTML 到 LaTeX 转换 BETA

convertHtmlToLatex(string $Input, array $Override = NULL)

因为我已经遇到了一个情况,其中发送到latex视图的数据是HTML格式,所以我决定添加一个解析器,它将基本的HTML字符串转换为LaTeX。包括一组HTML标签及其转换方式。**注意:在转换结束后,所有不在默认转换集或覆盖转换集中的HTML标签都将被strip_tags()移除**。

如果您需要这个功能,但需要某些HTML标签以不同的方式转换,可以向方法发送一个覆盖数组。这个覆盖数组需要如下所示

    $Override = array(
        array('tag' => 'img', 'extract' => 'src', 'replace' => '\begin{center}\includegraphics[scale=1]{$1}\end{center}'),
        array('tag' => 'body', 'extract' => 'value', 'replace' => '$1 \newline '),
    );

数组键的解释

以下代码片段显示了转换过程的工作方式

    $HTMLString = '<h1>Heading 1</h1> <p>Text</p> <h2>Heading 2</h2> <p>Text</p> <h3>Heading 3</h3> <p>Text</p> <p>Normal text here with some <strong>strong</strong> and <strong>bold</strong> text.</p> <p>There is also text that could be <u>underlined</u>.</p> <p>Or of course we could have <em>em-wrapped</em> or <em>i-wrapped</em> text</p> <p>A special test could be a <u><em><strong>bold, underlined and italic</strong></em></u> text at the same time!</p> <p>For the mathematicians we also have calculations x<sup>2</sup> and chemical stuff H<sub>2</sub>O</p> <p>We also have lists that needs to be shown. For example an unordered and an ordered list.</p> <p>If there is alot of text we might also want to use a line break <br> to continue on the next line.</p> <ul> <li>UL Item 1 <ul> <li>UL Item 1.1</li> <li>UL Item 1.2</li> </ul> </li> <li>UL Item 2</li> <li>UL Item 3</li> </ul> <ol> <li>UL Item 1</li> <li>UL Item 2</li> <li>UL Item 3</li> </ol> <p>Last but not least. We have images.</p> <img src="/images/testimages/image1.png" /> <img src="/images/testimages/image2.png" >';

    $Override = array(
        array('tag' => 'img', 'extract' => 'src', 'replace' => '\begin{center}\includegraphics[scale=1]{$1}\end{center}'),
        array('tag' => 'body', 'extract' => 'value', 'replace' => '$1 \newline '),
    );

    $LatexString = (new LaraTeX)->convertHtmlToLatex($HTMLString, $Override);

此示例将返回以下LaTeX字符串

\section{Heading 1} Text \newline \subsection{Heading 2} Text \newline \subsubsection{Heading 3} Text \newline Normal text here with some \textbf{strong} and \textbf{bold} text. \newline There is also text that could be \underline{underlined}. \newline Or of course we could have \textit{em-wrapped} or \textit{i-wrapped} text \newline A special test could be a \underline{\textit{\textbf{bold, underlined and italic}}} text at the same time! \newline For the mathematicians we also have calculations x\textsuperscript{2} and chemical stuff H\textsubscript{2}O \newline We also have lists that needs to be shown. For example an unordered and an ordered list. \newline If there is alot of text we might also want to use a line break \newline to continue on the next line. The br tag can have a leading slash too. \newline \begin{itemize} \item UL Item 1 \begin{itemize} \item UL Item 1.1 \item UL Item 1.2 \end{itemize} \item UL Item 2 \item UL Item 3 \end{itemize} \begin{enumerate} \item UL Item 1 \item UL Item 2 \item UL Item 3 \end{enumerate} Last but not least. We have images. \newline \begin{center}\includegraphics[scale=1]{/images/testimages/image1.png}\end{center} \begin{center}\includegraphics[scale=1]{/images/testimages/image2.png}\end{center} \newline

监听事件

每当一个PDF成功生成时,它会触发事件LaratexPdfWasGenerated。同样,每当PDF生成失败时,它会触发事件LaratexPdfFailed

这些事件对于您需要根据生成状态执行某些操作非常重要,如更新数据库。但通常这些PDF文件具有一些元数据,如PDF所属的用户或PDF所属的订单。您可以在实例化LaraTeX时作为第二个参数传递这些元数据。

然后,这些元数据会从触发的事件返回给您,这使得监听变得更加有意义。元数据可以是任何东西,可以是字符串、数字、数组、对象、集合等等。您可以根据您需要的逻辑传递元数据。

// $user will be our metadata in this example
$user = Auth::user();

(new LaraTeX('latex.tex', $user))->with([
    'Name' => 'John Doe',
    'Dob' => '01/01/1990',
    'SpecialCharacters' => '$ (a < b) $',
    'languages' => [
        'English',
        'Spanish',
        'Italian'
    ]
])->savePdf(storage_path('app/pdf/test.pdf'));

然后,您可以定义一个监听器,如下所示

<?php

namespace App\Listeners;

use Ismaelw\LaraTeX\LaratexPdfWasGenerated;

class LaratexPdfWasGeneratedConfirmation
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  LatexPdfWasGenerated  $event
     * @return void
     */
    public function handle(LaratexPdfWasGenerated$event)
    {
        // Path of PDF in case it was saved
        // OR
        // Downloaded name of PDF file in case it was downloaded in response directly
        $pdf = $event->pdf;

        // download OR savepdf
        $action = $event->action;

        // metadata $user in this example
        $user = $event->metadata;

        // Perform desired actions
    }
}

垃圾回收

当您导出PDF时,pdflatex会生成一些额外的文件与您的PDF文件一同生成(例如.aux.log.out等)。该包内部处理垃圾回收过程。它确保在生成PDF或发生任何错误时,没有文件残留。

这确保服务器不会浪费空间来保存这些文件。

错误处理

我们使用来自texlive的应用程序pdflatex来生成PDF。如果您的tex文件中出现语法错误,它将错误记录到日志文件中。或者如果它被关闭,它将在控制台显示输出。

该包内部也处理相同的逻辑并抛出ViewNotFoundException异常。异常将包含有关错误的全部信息,便于您进行调试。

贡献

如果您想为此包添加新功能,请随时贡献。

致谢

此包在很大程度上受到了Techsemicolon创建的laravel-php-latex包的启发。Techsemicolon后来,由于其他包缺少支持,我开始创建自己的laravel-php-latex版本ismaelw/laravel-php-latex

为了更好的兼容性和更好的配置处理,我决定创建这个包。

感谢@koona-labs的贡献,现在您可以使用Blade指令。

变更日志

有关任何重大变更的更多信息,请参阅变更日志

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可文件