使用 LaTeX,要本地构建图形,可以使用模式picture
。如今,还有pstricks
和TikZ
包。最初如何在 TeX 中绘制图形?这可能吗?我在 TeXbook 中找不到任何相关信息。
答案1
免责声明:此答案中的一些片段和事实可能不准确(大多数历史事实都来自网络上),因此请谨慎对待(欢迎更正)。
DVI 格式
TeX 的 DVI 格式是生成排版输出的最简单的格式:它只知道框、粘合和规则。字符是一个引用字体文件中字形的框,可能通过某种粘合与下一个框隔开。DVI 不知道字形本身;它只将它们链接到字体文件,而 DVI 查看器/打印机负责将字形放置在适当的框中(有一个“DVI 标准”你可能会觉得很有趣)。这里没有空间放图形,所以 Knuth 为 TeX 的输出添加了另一个项目(出于这个和其他目的):a \special
;这个原语直接向输出文件添加一些命令,这个命令应该由输出设备解释(例如,使用\special{pdf:q 1 j 0 1 0 rg 0 0 1 RG 0 0 10 10 re B Q}\bye
(来自samplepdf.tex
运行 pdfTeX 你会得到在输出中)。
这些\specials
需要能够理解它们的输出设备。PDF 格式只是1993年创建,从 TeX 的历史来看,这是最近的。在此之前,我们有 PostScript1984年创建,比 TeX 晚一点。但是dvips
,用于将 TeX 的 DVI 输出转换为 PostScript 文件的程序直到 1986 年左右才创建(参见这次采访与dvips
创作者 Tomas Rokicki 合作)。
使用字体绘图
在创建之前,dvips
他们只有带有字体字形的框,所以他们使用了它们。Knuth 在 TeXbook 的附录 D 中说道:
如果您喜欢制作图片,而不是排版普通文本,那么 TeX 将会为您带来无尽的挫败感/乐趣,因为只要您有合适的字体,几乎一切皆有可能。
(这tikz-pgf标签在这里证明该陈述是多么真实 :-)
其中一个例子就是manfnt
字体,在 TeX 和 METAFONT 书籍中用于各种字形。例如,如果您执行以下操作:
\font\qc=manfnt
\qc abcd \char127
\bye
您将获得四个四分之一圆,分别存储在a
、b
和该字体的位置,c
以及d
著名的危险弯道标志:
Knuth 还展示了一些方便的宏,用于这些四分之一圆,以便您可以根据它们绘制图片。给出的一个例子是龙曲线,它使用了精心放置的框,其中包含a
、b
、c
和:d
manfnt
\font\qc=manfnt
\catcode`\ =9 \endlinechar=-1 % ignore all spaces (temporarily)
\newcount\dir \newdimen\y \newdimen\w
\newif\ifvisible \let\B=\visibletrue \let\W=\visiblefalse
\newbox\NE \newbox\NW \newbox\SE \newbox\SW \newbox\NS \newbox\EW
\setbox\SW=\hbox{\qc a} \setbox\NW=\hbox{\qc b}
\setbox\NE=\hbox{\qc c} \setbox\SE=\hbox{\qc d}
\w=\wd\SW \dimen0=\fontdimen8\qc
\setbox\EW=\hbox{\kern-\dp\SW \vrule height\dimen0 width\wd\SW} \wd\EW=\w
\setbox\NS=\hbox{\vrule height\ht\SW depth\dp\SW width\dimen0} \wd\NS=\w
\def\L{\ifcase\dir \dy+\NW \or\dx-\SW \or\dy-\SE \or\dx+\NE\dd-4\fi \dd+1}
\def\S{\ifcase\dir \dx+\EW \or \dy+\NS \or \dx-\EW \or \dy-\NS \fi}
\def\R{\ifcase\dir \dy-\SW\dd+4 \or\dx+\SE \or\dy+\NE \or\dx-\NW\fi \dd-1}
\def\T{\ifcase\dir\kern-\w\dd+2\or\ey-\dd+2\or\kern\w\dd-2\or\ey+\dd-2\fi}
\edef\dd#1#2{\global\advance\dir#1#2\space}
\def\dx#1#2{\ifvisible\raise\y\copy#2 \if#1-\kern-2\w\fi\else\kern#1\w\fi}
\def\dy#1#2{\ifvisible\raise\y\copy#2 \kern-\w \fi \global\advance\y#1\w}
\def\ey#1{\global\advance\y#1\w}
\def\path#1{\hbox{\B \dir=0 \y=0pt #1}}
\catcode`\ =10 \endlinechar=`\^^M % resume normal spacing conventions
\newcount\n % the current order in the \dragon and \nogard macros
\def\dragon{\ifnum\n>0{\advance\n-1 \dragon\L\nogard}\fi}
\def\nogard{\ifnum\n>0{\advance\n-1 \dragon\R\nogard}\fi}
% end of definitions
\centerline{\path{\dir=3 \n=9 \dragon}}
\bye
生成结果:
如果你替换manfnt
为,cmtt10
你可以清楚地看到:
LaTeX(当时是 2.09)也采用了同样的方法;资料中提到的最早日期是 1991/08/14,但有一篇由 Rainer Schöpf 撰写的 TUG Boat 文章自 1989 年起,因此它可能存在的时间比这要长得多),在环境中实现picture
。环境有一组命令,如、、\put
等,它们使用包含线段和圆段的特制字体,并将这些线段一个接一个地放置以形成图片。\line
\circle
要查看这些字体的实际效果,您可以尝试:
\documentclass{article}
\begin{document}
{\tenln abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\par}
\end{document}
查看一些可用的字形(或使用包fonttable
):
这种方法的问题在于,字体文件中只能有这么多不同的线条,一旦你开始尝试,你就会发现它的局限性。例如,你只有有限数量的线条角度,介于两者之间的任何角度都不起作用(尝试\put(0,0){\vector(259,966){1}}
),你还有固定步长和最大半径的圆圈(尝试\put(0,0){\circle{40}} \put(0,0){\circle{50}}
使用默认值\unitlength
)。看看pict2e
包装文档:它包含一些基于传统字体的图片和“现代”(PostScript 之后,所以是 1985 年 :-) 技术的很好的比较。
Knuthmanfnt
和 LaTeX line10
(中的字体\tenln
)都是使用 METAFONT 制作的,因此奇怪的是,它是第一批用于在 TeX 中原生添加图形(或尽可能接近)的软件之一。
派克泰克
Michael Wichura 在 80 年代末开发了一款相当有趣的软件,那就是 PiCTeX(TUGboat 文章)。PiCTeX 是一个宏包,它定义了自己的绘图机制:为了绘制曲线,它使用几个相邻的点进行重叠,以便在阅读距离上它看起来像一条连续的线。
该包定义了自己的绘图语法,例如绘制一个以 (0,0) 为中心、从 (5,0) 到 (0,5) 的四分之一圆,您可以使用:
\beginpicture
\circulararc 90 degrees from 5 0 center at 0 0
\endpicture
您可以manfnt
在上面的 Dragon Curve 中用 PiCTeX 绘制的四分之一圆替换字符,以获得相同的曲线(在答案的末尾对此进行编码,以避免混乱)。如果放大,您可以看到点:
你可以用 来\setplotsymbol(<symbol>)
代替点<symbol>
(\setplotsymbol(?)
效果不错)并\plotsymbolspacing=<distance>
设置点之间的距离。请注意,PiCTeX 使用很多TeX 必须将所有点都保存在同一页中,因此你可能会很快耗尽内存(事实上,对于非平凡的图片,这种情况在过去经常发生)。你可以找到 PiCTeX 命令的摘要这里以及一些有用的例子这里以及来源列表这里。遗憾的是,尽管 PiCTeX 是免费的,但其手册却不是(你可以订购一份这里但我怀疑它如今除了满足历史好奇心以外是否还有其他用处)。
后记
几年后,Adobe 开发了一种成熟的编程语言 PostScript(或者说 Adobe 就是从 PostScript 中诞生的)。它的主要目的是成为一种页面描述语言,因此其大部分功能与 TeX 的 DVI 一致,但它具有更多功能,包括从一开始就具有绘图和(有限的)颜色支持。然后,1986 年,Tomas Rokicki 编写了dvips
将 DVI 文件转换为 PostScript 的程序,这样 TeX 就可以从中受益。这允许使用\special
s 将 PostScript 代码写入页面,因此可以使用类似这样的代码:
\special{ps:
50 50 scale
newpath 1 1 moveto
0 1 rlineto 1 0 rlineto
0 -1 rlineto -1 0 rlineto
closepath
gsave 0 1 0 setrgbcolor fill grestore
0 0 1 setrgbcolor
0.05 setlinewidth
stroke}
\bye
tex <file>.tex
然后运行,dvips <file>.dvi
你会得到<file>.ps
一个,类似于之前的 PDF 命令。
但请记住,TeX 仍然对颜色或绘图一无所知:它只是将其转发\special
到 DVI 文件,然后dvips
拾取并传递给 PostScript 设备来执行其操作。
过了一段时间(大约 1993 年)[1,2] ) Timothy Van Zandt 将 PSTricks(PS 代表 PostScript)带到了这个世界:TeX 和 LaTeX 的 PostScript 图形宏接口。这个宏层提供了带有键值接口的命令,最终将其转换为 PostScript \special
。上面示例中的图片可以重写为:
\input pstricks
\psframe[fillstyle=solid,linecolor=blue,
fillcolor=green](1,1)
\bye
当时,Adobe 正在开发 PDF 格式,该格式原本是 PostScript 的一个独立于系统的版本。它不包含编程语言,但也有绘图和颜色功能。几年后(1996 年[1] Hàn Thế Thành 编写了 pdfTeX:一个能够直接生成 PDF 文件的 TeX 引擎,而不必使用dvips
。ps2pdf
这将允许您执行与 PostScript 大致相同的操作,但使用 PDF 命令:
\special{pdf:
q
1 j
0 1 0 rg
0 0 1 RG
0 0 10 10 re
B
Q}
\bye
进入 21 世纪和用户界面
随着计算机和计算能力的日益普及,其他几种软件也加入进来。主要的软件有 XeTeX,它具有更新的输出格式 eXtended DVI (XDV),LuaTeX,它使用与 pdfTeX 类似的后端,但有一些怪癖,可以输出为 Web 格式,如 HTML(使用tex4ht
)和 SVG(使用dvisvgm
)。有了所有这些选项,图形和颜色的基本支持包采用了一个称为“驱动程序(或后端)特定文件”的抽象层。这使得事情变得容易得多,因为在宏层您可以说\driver@line{0,0}{10,10}
,那么\driver@line
宏就会有适当的定义(大致)\special{pdf:0 0 m 10 10 l b}
如果输出格式是 PDF 还是\special{ps:0 0 moveto 10 10 lineto stroke}
PostScript 或<path d="M 0 0 l 10 10" />
SVG(仅举一个例子,这些命令的输出会有所不同在大小和位置上)。
pict2e
诸如扩展 LaTeX 环境的软件包picture
可以产生与传统基于字体的输出大致相同的内容,但它使用这些特定于驱动程序的例程,因此您可以在任何您喜欢的引擎中使用该软件包,驱动程序文件将为您处理这些事情。
从这一点开始,在宏观层面上创建了进一步的抽象,以便于(可能适用条款和条件)在 LaTeX 中绘图。其中最大的例子是 Ti钾Z,它增加了大量的用户友好性,使\draw (0,0) -- (10,10);
翻译最终成为\driver@line{0,0}{10,10}
然后\special{pdf:0 0 m 10 10 l b}
。当然,事情并没有那么简单,但原理是一样的。
再说一次,这并不完整,也不完全准确(大部分内容发生在我还是个小孩的时候),但我希望它能让你了解当时的情况。
PiCTeX Dragon Curve 的代码
\input pictex
% \setplotsymbol(?)
% \plotsymbolspacing=1pt
% \plotsymbolspacing=0.01pt % exceeds TeX's memory
\def\pictx#1#2#3#4{%
\beginpicture
\setplotarea x from -5 to 5, y from -5 to 5
\circulararc 90 degrees from #1 #2 center at #3 #4
\endpicture}
\font\qc=manfnt
\catcode`\ =9 \endlinechar=-1 % ignore all spaces (temporarily)
\newcount\dir \newdimen\y \newdimen\w
\newif\ifvisible \let\B=\visibletrue \let\W=\visiblefalse
\newbox\NE \newbox\NW \newbox\SE \newbox\SW \newbox\NS \newbox\EW
\setbox\SW=\hbox{\pictx{-5}{-5}{-10}{-5}}
\setbox\NW=\hbox{\pictx{-10}{0}{-10}{5}}
\setbox\NE=\hbox{\pictx{-5}{5}{0}{5}}
\setbox\SE=\hbox{\pictx{0}{0}{0}{-5}}
\w=\wd\SW \dimen0=\fontdimen8\qc
\setbox\EW=\hbox{\kern-\dp\SW \vrule height\dimen0 width\wd\SW} \wd\EW=\w
\setbox\NS=\hbox{\vrule height\ht\SW depth\dp\SW width\dimen0} \wd\NS=\w
\def\L{\ifcase\dir \dy+\NW \or\dx-\SW \or\dy-\SE \or\dx+\NE\dd-4\fi \dd+1}
\def\S{\ifcase\dir \dx+\EW \or \dy+\NS \or \dx-\EW \or \dy-\NS \fi}
\def\R{\ifcase\dir \dy-\SW\dd+4 \or\dx+\SE \or\dy+\NE \or\dx-\NW\fi \dd-1}
\def\T{\ifcase\dir\kern-\w\dd+2\or\ey-\dd+2\or\kern\w\dd-2\or\ey+\dd-2\fi}
\edef\dd#1#2{\global\advance\dir#1#2\space}
\def\dx#1#2{\ifvisible\raise\y\copy#2 \if#1-\kern-2\w\fi\else\kern#1\w\fi}
\def\dy#1#2{\ifvisible\raise\y\copy#2 \kern-\w \fi \global\advance\y#1\w}
\def\ey#1{\global\advance\y#1\w}
\def\path#1{\hbox{\B \dir=0 \y=0pt #1}}
\catcode`\ =10 \endlinechar=`\^^M % resume normal spacing conventions
\newcount\n % the current order in the \dragon and \nogard macros
\def\dragon{\ifnum\n>0{\advance\n-1 \dragon\L\nogard}\fi}
\def\nogard{\ifnum\n>0{\advance\n-1 \dragon\R\nogard}\fi}
% end of definitions
\centerline{\path{\dir=3 \n=9 \dragon}}
\bye