LaTeX 在 PHP 中使用公式和图形
可以肯定地说,Weblog 和 wiki 网站的世界将长期存在。尽管这些系统非常适合期刊、通用文本发布甚至摄影,但在需要使用比简单文本输入和图像更高级的功能的环境中工作时,它们的局限性变得显而易见。特别是,技术 Weblog 需要支持图形、数学表达式、图表等。仅使用 HTML 实现此类功能非常困难,甚至是不可能的。
使用外部应用程序(如 dia、xfig 和 Microsoft 公式编辑器)同样困难,因为发布者首先必须创建图形或数学公式,然后将图像表示上传到网站。此外,如果协作 Weblog 中的其他发布者想要修改图形,他们还必须拥有该应用程序以及创建图像的原始文件。显然,这种系统存在许多复杂性,并且会降低网站图形和公式的整体质量。
在本文中,我将演示如何在 PHP 中使用 LaTeX(一种专门为技术文档准备而设计的排版工具和语言)来满足这些需求。当 HTML 不足以满足这些复杂需求时,我从 PHP 中调用 LaTeX,然后将结果统一渲染为 PNG 图像,这是一种所有现代浏览器都支持的格式。由于该软件完全在服务器上可用,因此所有发布者和用户都可以访问相同的工具和软件包进行发布。
为什么不用 MathML?
根据 W3C 的说法,MathML 是一种用于描述数学的低级 XML 规范。虽然 MathML 是人类可读的,但在除最简单的情况外的所有情况下,作者都需要使用公式编辑器或其他实用程序来为他们生成 XML 代码。此外,现代浏览器仅支持 MathML 语言的有限子集,即使如此,许多浏览器也需要外部插件来支持 MathML。尽管这种语言的未来非常光明,但就目前而言,它基本上不受支持且无法使用。
更复杂的是,Leslie Lamport 的 LaTeX 排版系统已成为技术和科学文档制作的事实标准。LaTeX 基于 Donald Knuth 在 1970 年代初期的 TeX 文档布局系统,自 1994 年以来一直存在,并且是一个成熟且广为人知的技术文档准备平台,拥有忠实的用户群。这并不是说学习 LaTeX 很容易。当然不是,但就目前而言,MathML 没有提供令人信服的证据来保证从这个已经建立的系统进行过渡。
遵循 UNIX “编写程序以协同工作”的理念,我使用 Linux 平台上可用的常用工具组合,并将它们链接在一起以生成 LaTeX 源代码的 PNG 等效渲染。具体来说,您需要一个最近版本的 LaTeX,其中包含 dvips 和 ImageMagick 工具包。您将使用 ImageMagick 工具中的 convert 实用程序将结果转换为 PNG 图像。幸运的是,大多数提供 shell 访问权限的托管服务提供商已经提供了这些实用程序。
渲染系统接收一段文本字符串,并提取用 [tex] 和 [/tex] 对括起来的段,以供将来替换。这些提取的段称为 thunk。如果 thunk 之前已处理过,这意味着 thunk 代码的图像表示已经可用,则 thunk 将被替换为指向该图像的 URL。如果 thunk 是新的,则将其传递给 LaTeX 排版器,后者将其结果输出为 DVI 文件。然后,DVI 文件使用 ImageMagick 转换为 PNG 图像,并放置到缓存目录中。新创建图像的 URL 将替换原始文本中的 thunk。当所有 thunk 都已处理完毕后,生成的文本将返回给调用者。图 1 说明了转换单个 thunk 的过程。
我认为最好自上而下地开始,首先了解如何调用渲染过程,而无需讨论实现规范。驱动程序只是一个 HTML 前端,它提供了一种测试 LaTeX 渲染系统的机制。它使您能够了解应如何调用 render 类。为了帮助您入门,我提供了清单 1 中显示的基本模板。
清单 1. render_example.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html> <head> <title>LaTeX Equations and Graphics in PHP</title> </head> <body> <!-- form to enter LaTeX code --> <form action="render_example.php" method="post"> <textarea rows="20" cols="60" name="render_text"></textarea><br /> <input name="submit" type="submit" value="Render" /> </form> <?php if (isset($_POST['submit'])) { echo '<h1>Result</h1>'; require('render.class.php'); $text = $_POST['render_text']; if (get_magic_quotes_gpc()) $text = stripslashes($text); $render = new render(); echo $render->transform($text); } ?> </body> </html>
此 PHP 页面提供了一个用于输入 LaTeX 代码的表单,然后通过 transform 方法将 thunk 替换为指向渲染 PNG 图像的 URL。其他一切都在 render 类中幕后完成。
清单 2. render.php
class render { var $LATEX_PATH = "/usr/local/bin/latex"; var $DVIPS_PATH = "/usr/local/bin/dvips"; var $CONVERT_PATH = "/usr/local/bin/convert"; var $TMP_DIR = "/usr/home/barik/public_html/gehennom/lj/tmp"; var $CACHE_DIR = "/usr/home/barik/public_html/gehennom/lj/cache"; var $URL_PATH = "http://www.barik.net/lj/cache"; function wrap($text) { ... } function transform($text) { ... } function render_latex($text) { ... } }
您需要让 PHP 知道您的工具位于何处,并提供一个目录,供 PHP 可以在其中写入临时文件并存储其缓存。为了方便起见,还需要 URL_PATH。此 URL_PATH 在 HTML 中生成图像标签时使用。
不要被简单性所迷惑。您可以传递给 LaTeX 和 ImageMagick 的大量选项可用于修改输出 PNG 图像,您应该全部探索它们。在这里,我只是提供了框架。
wrap 方法接受您的 LaTeX thunk,并用序言和尾声将其包围起来,以创建有效的 LaTeX 源文件。您可以将此视为等同于向 C 文件添加额外的包含或在 Java 中导入包以扩展语言的功能(清单 3)。
清单 3. wrap.php7870l3.qrk
function wrap($thunk) { return <<<EOS \documentclass[10pt]{article} % add additional packages here \usepackage{amsmath} \usepackage{amsfonts} \usepackage{amssymb} \usepackage{pst-plot} \usepackage{color} \pagestyle{empty} \begin{document} $thunk \end{document} EOS; }
如您所见,我包含了我在 LaTeX 包装器中经常需要的软件包。因此,我包含了美国数学学会 (AMS) 软件包,它提供了额外的数学构造,以及 PSTricks 软件包来渲染矢量图形。页眉样式设置为 empty,以便图像上不显示页码。此外,thunk 插入在文档块之间。
并非所有这些软件包都可以在您的系统上使用。如有必要,您可以从综合 TeX 存档网络 (CTAN) 网站(请参阅在线资源)下载其他软件包,以扩展您的基本 LaTeX 系统的功能。例如,可以下载用于条形图、UML 表示法甚至卡诺图的软件包。无论您的需求如何,该存储库都值得一看。
清单 4. render_latex.php
function render_latex($thunk, $hash) { $thunk = $this->wrap($thunk); $current_dir = getcwd(); chdir($this->TMP_DIR); // create temporary LaTeX file $fp = fopen($this->TMP_DIR . "/$hash.tex", "w+"); fputs($fp, $thunk); fclose($fp); // run LaTeX to create temporary DVI file $command = $this->LATEX_PATH . " --interaction=nonstopmode " . $hash . ".tex"; exec($command); // run dvips to create temporary PS file $command = $this->DVIPS_PATH . " -E $hash" . ".dvi -o " . "$hash.ps"; exec($command); // run PS file through ImageMagick to // create PNG file $command = $this->CONVERT_PATH . " -density 120 $hash.ps $hash.png"; exec($command); // copy the file to the cache directory copy("$hash.png", $this->CACHE_DIR . "/$hash.png"); chdir($current_dir); }
thunk 参数很明显:它是我们当前正在检查的 LaTeX 代码块。hash 参数是 thunk 的统一版本,本质上是文件名库的 md5。
我更改为临时目录并将 thunk 写入临时 LaTeX 文件。然后,LaTeX 创建一个 DVI 文件。命令行参数告诉 LaTeX 以非交互方式运行。生成的 DVI 文件通过使用 dvips 转换为 PostScript,-E 选项指定边界框。然后,我通过 convert(即程序名称)运行生成的 PostScript 文件,以将文件转换为 PNG 图像。convert 工具有一系列选项,最适合您的设置取决于您的网站。
最后,请注意,exec 命令返回失败状态代码。为了简洁起见,我省略了错误检查,并且始终假设所有步骤都成功。LaTeX 也有一些危险的命令,对于多用户网站来说可能是一个问题。因此,如果在 thunk 中找到某些关键字,则返回错误可能是明智之举。
当出现问题时
如果在渲染阶段出现问题,您可以尝试使用 shell 手动处理 LaTeX 文件,并使用以下命令进行诊断
latex --interaction=nonstopmode my.tex dvips -E my.dvi -o my.ps convert -density 120 my.ps my.png
这使您可以隔离 LaTeX 渲染器失败的特定步骤。
清单 5. cleanup.php
function cleanup($hash) { $current_dir = getcwd(); chdir($this->TMP_DIR); unlink($this->TMP_DIR . "/$hash.tex"); unlink($this->TMP_DIR . "/$hash.aux"); unlink($this->TMP_DIR . "/$hash.log"); unlink($this->TMP_DIR . "/$hash.dvi"); unlink($this->TMP_DIR . "/$hash.ps"); unlink($this->TMP_DIR . "/$hash.png"); chdir($current_dir); }
清单 6. transform.php
function transform($text) { preg_match_all("/\[tex\](.*?)\[\/tex\]/si", $text, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $position = strpos($text, $matches[0][$i]); $thunk = $matches[1][$i]; $hash = md5($thunk); $full_name = $this->CACHE_DIR . "/" . $hash . ".png"; $url = $this->URL_PATH . "/" . $hash . ".png"; if (!is_file($full_name)) { $this->render_latex($thunk, $hash); $this->cleanup($hash); } $text = substr_replace($text, "<img src=\"$url\" alt=\"Formula: $i\" />", $position, strlen($matches[0][$i])); } return $text; }
PHP 中的 preg_match_all 函数提取 thunk 以及每个 thunk 的位置。然后,通过循环单独解析每个 thunk。接下来,创建 thunk 文本的唯一 md5。这告诉我们 thunk 以前是否已缓存。如果 thunk 尚未缓存,我将调用 LaTeX 渲染器方法并立即清理生成的临时文件。在任何一种情况下,thunk 都会被替换为 URL。当所有 thunk 都处理完毕后,文本将被返回。
现在,让我们看几个示例,说明您可以在 LaTeX 的帮助下渲染的公式类型。这些公式中的大多数取自 Helmut Kopka 和 Patrick W. Daly 的 LaTeX 指南,许多人认为这是关于 LaTeX 系统的基本书籍之一。

图 2. 示例:分数
[tex] \begin{displaymath} \frac{a^2 - b^2}{a + b} = a - b \end{displaymath} [/tex]

图 3. 示例:两个变量 X 和 Y 的相关性
[tex] \begin{displaymath} \mathop{\mathrm{corr}}(X,Y)= \frac{\displaystyle \sum_{i=1}^n(x_i-\overline x) (y_i-\overline y)} {\displaystyle\biggl[ \sum_{i=1}^n(x_i-\overline x)^2 \sum_{i=1}^n(y_i-\overline y)^2 \biggr]^{1/2}} \end{displaymath} [/tex]
[tex] \begin{displaymath} I(z) = \sin( \frac{\pi}{2} z^2 ) \sum_{n=0}^\infty \frac{ (-1)^n \pi^{2n} }{1 \cdot 3 \cdots (4n + 1) } z^{4n + 1} -\cos( \frac{\pi}{2} z^2 ) \sum_{n=0}^\infty \frac{ (-1)^n \pi^{2n + 1} }{1 \cdot 3 \cdots (4n + 3) } z^{4n + 3} \end{displaymath} [/tex]
尽管 LaTeX 是一个强大的数学排版工具,但在 PSTricks 等软件包的帮助下,它在其他领域也很有能力。这些图由 Herbert Voss 提供。在他的网站(请参阅资源)上,您可以找到更多使用 PSTricks 测试 LaTeX 渲染系统的示例。但是,要使他的一些更高级的示例正确显示,可能需要付出相当大的努力。

图 5. 示例:10x ex 和 2x 的图
[tex] \psset{unit=0.5cm} \begin{pspicture}(-4,-0.5)(4,8) \psgrid[subgriddiv=0,griddots=5, gridlabels=7pt](-4,-0.5)(4,8) \psline[linewidth=1pt]{->}(-4,0)(+4,0) \psline[linewidth=1pt]{->}(0,-0.5)(0,8) \psplot[plotstyle=curve, linewidth=0.5pt]{-4}{0.9}{10 x exp} \rput[l](1,7.5){$10^x$} \psplot[plotstyle=curve,linecolor=red, linewidth=0.5pt]{-4}{3}{2 x exp} \rput[l](2.2,7.5){\color{blue}$e^x$} \psplot[plotstyle=curve,linecolor=blue, linewidth=0.5pt]{-4}{2.05}{2.7183 x exp} \rput[l](3.2,7.5){\color{red}$2^x$} \rput(4,8.5){\color{white}change\normalcolor} \rput(-4,-1){\color{white}bounding box\normalcolor} \end{pspicture} [/tex]

图 6. 示例:Ceil 函数
[tex] \SpecialCoor \begin{pspicture}(-3,-3)(3,3) \multido{\i=-2+1}{6}{% \psline[linewidth=3pt,linecolor=red] (\i,\i)(! \i\space 1 sub \i)}% \psaxes[linewidth=0.2mm]{->}(0,0)(-3,-3)(3,3) \end{pspicture} [/tex]
如今,Web 上有几种 LaTeX 渲染器的实现,其中一些比另一些效果更好。例如,Steve Mayer 现在维护着 Benjamin Zeiss 的原始 PHP LaTeX 渲染器。Mayer 还为常见的 Weblog 系统(包括 WordPress)编写了几个插件。如果您想要适用于您网站的可插拔解决方案,我推荐这个。
此外,John Walker 提供了 textogif,这是一个 Perl 程序,它使用 LaTeX2HTML 工具通过 CGI 以 GIF 或 PNG 格式渲染图像。最后,John Forkosh 提供了 mimeTeX,它是使用 CGI 通过 C 编写的。它的优点是不需要 LaTeX 或 ImageMagick,但代价是渲染质量。
最初将 LaTeX 与您的 wiki 或 Weblog 集成可能看起来是一项艰巨的任务。但是,一旦您掌握了窍门,您就会想知道没有它您是如何生活的。使用此模型,您还可以看到除了 LaTeX 之外,其他语言也可以如何嵌入到 PHP 中。要考虑的其他想法包括使用 Gnuplot 生成绘图,使用 Octave 评估复杂表达式或使用 POV-Ray 渲染 3D 场景。
如今,Weblog 社区所代表的主题在很大程度上是不成比例的。事实上,编程领域之外的许多技术作家一直远离 Weblog,仅仅是因为轻松传达他们想法的方式不存在。我希望在 Web 上使用 LaTeX 渲染系统能够弥合这一关键差距。
Titus Barik 是一位小型企业的 IT 顾问。他也是一位活跃的 Weblogger 和技术书虫。您可以访问他的 Weblog:barik.net。