陈颂光
全栈工程师,承接从编译器到网站的各类软件开发与咨询,也可以聊历史哲学。
关注我的 GitHub

Groff排版概览

groff是roff排版系统现在最常用的实现,其它实现有troff、nroff、ditroff等等。尽管历史悠久,roff当前还在广泛使用中,例如,UNIX系统的man手册页、很多软件书籍和标准是用roff来写的。一个 roff 排版系统包含一个可扩展的文本格式化语言和一系列程序用以打印和转换为其他文本格式。 传统上,它是Unix的主要文本处理系统;现在,每个类Unix操作系统仍然附带一个roff系统作为核心软件包。roff在文本设备上的输出效果仍然是无可比拟的,并且,与其他自由的排版系统相比,它的图形输出也不差,甚至强于很多商业的系统。groff支持html、pdf、ps、dvi等输出格式,也可以通过X11查看器在屏幕渲染。

如果你想快速学会用groff,例如马上去写man文档,从模仿现有的开始总没错,打开/usr/share/man目录下的压缩包就是了。

历史

roff 文本处理系统有很长的一段历史,可以回溯到60 年代。 roff 系统自身与 Unix 操作系统关系密切, 但是它的起源要从更早的操作系统 CTSS 和 Multics 开始。

roff 系统的演变与操作系统的历史紧密联系。 它的 “先祖” runoff 是 Jerry Saltzer 在 CTSS 操作系统 (Compatible Time Sharing System,1961)上的作品。 CTTS后来发展成为操作系统Multics⟨http://www.multicians.org⟩,Unix的一个著名的先驱,出现于1963,同时runoff成为文档和文本处理的主要格式。当时,这两种操作系统只能运行在非常昂贵的计算机之上, 因此它们大部分用于研究和官方及军队的任务之中。

与现代的 roff 相比, runoff 语言可以做的事非常有限。 在 60 年代,只能产生文本的输出。 这可以用一个长度为 2 的命令的集合实现, 这些命令的绝大部分都保持不变地被 roff采用。 这种语言的模型是根据 “前计算机时代” 的排版习惯而建立的, 那时,以点.开头的行被写在手稿中, 向之后进行手工排版工作的工人指示格式化的要求。开始时,runoff 程序是用 PL/1 语言写成的,后来用 BCPL 来写--那是 C 语言的 “祖母”。 在 Multics 操作系统中,帮助系统由 runoff 来处理, 与 roff 管理 Unix 手册页的作用类似。

在 70 年代,Multics 的后裔 Unix 变得越来越普遍,因为它可以运行在廉价的计算机上, 并且那时在大学里可以很容易地获得。 在 MIT, 有人想在一台运行 Unix 的 PDP-11 计算机上驱动 Wang 的 Graphic Systems CAT 排字机,一种图形化的输出设备。 由于 runoff 在这种任务上能力有限,它被 Josef F.Osanna, (Multics 操作系统的主要开发者之一,几个 runoff 移植系统的作者) 继续开发,成为一种更强大的文本排版系统。runoff 这个名字被简化为 roff. Ocsanna 所设想的,极大扩展了的语言已经包含了一个完整的 roff 系统的所有元素。 所有现代的 roff 系统都试图实现同这个系统兼容。 因此 Joe Osanna是当之无愧的所有 roff 系统之父。

最早的 roff 系统有三个排版程序:troff (typesetter roff) 为它唯一支持的 CAT 排字机产生一个图形的输出、nroff 为终端和文本打印机产生合适的文本输出、roff 是对最初的 runoff 程序的有限功能进行重新实现并在后来的版本中被抛弃。 现在, roff 这个名字只用来指代一个 troff/nroff 系统的整体。Osanna 的第一版用 PDP-11 的汇编语言实现,发布于 1973。 Brian Kernighan 加入到 roff 的开发中,使用 C 语言将它进行了重写,C 版本发布于 1975。

1977 年,Osanna 在他 50 岁时,由于一次突发的心脏病而去世。 Kernighan 继续开发 troff。 下一个开发目标是赋予 troff 一个更一般的接口,以支持更多设备, 中间输出格式和 “后处理” 系统。这使得 roff 系统的结构趋于完整,现在仍然被使用。这个新的 troff 版本是所有现存的较新的 troff 系统的基础,包括 groff。在一些系统上,这个设备无关的 troff 有一个可执行文件叫做 ditroff。 所有现代的 troff 程序都已经自动提供了对 distroff 完整的兼容性。

最重要的自由 roff 项目是 GNU 移植版本的 troff, 由 James Clark 建立,使用 GNU Public License。它叫做 groff (GNU roff)。groff 系统仍然在继续开发。 它与传统 troff 兼容,但是还添加了很多扩展。 它是第一个可以在几乎所有操作系统上运行的 roff 系统并且它是自由开放的。 这使得 groff 成为现在 roff的事实标准。

架构

roff 系统由预处理器、排版程序和后处理器组成。这种架构大量使用管道机制:

sh# cat file | ... | preproc | ... | troff options | postproc

预处理器产生 roff 代码,传给一个 roff 处理器, 然后 roff 处理器接下来产生中间输出,传给一个后处理器程序, 用来打印或者产生最终输出。

所有这些组件都使用它们自己的语言,每种语言是与其他组件完全无关的。 此外,还可以包括为特殊用途的 roff 宏包。

大多数 roff 文档中掺杂着使用一些包中的宏、 一个或多个预处理器的代码,还会添加 roff 语言中的一些元素。 用户很少需要用到 roff 排版语言的完整功能; 只有宏包的作者需要知道底层细节。

预处理器

预处理器是任何产生符合 roff 排版语言语法的输出的程序。每个预处理器都有它自己的语言,在预处理器运行时被翻译为roff代码。roff文档中可以包含以这种语言写成的片段;它们可以被特殊的 roff 命令或宏识别。 加入了预处理器代码的文档必须通过所有相应的预处理器处理之后, 才能传给真正的 roff排版程序, 因为真正的 roff 排版程序会忽略所有陌生的代码。 预处理器只会分析并转换指定由它处理的文档部分。

有大量的自由/商业 roff 预处理器。 一些不能在所有系统上使用, 还有一些预处理器被认为是 roff 系统不可分割的部分。 传统的预处理器有:

预处理器 用途
tbl 制表
eqn 数学公式
pic 绘图
refer 书目索引
soelim 包含标准位置的宏文件

其他已知预处理器,但不是在所有系统上都可用,包括 预处理器|用途 —|— chem|化学公式 grap|构造图元 grn|插入gremlin图片

###排版程序

排版程序是一个解释用roff排版语言或roff宏包写成的文档的程序。它产生中间结果将送入单一设备后处理器。后处理器必须在排版程序的命令行选项中指定。 文档必须已经通过了所有需要的预处理器处理。

roff 排版程序的输出以另外一种语言表示,类似于一种汇编语言。 产生的中间输出是为一种特定的设备优化过的, 但是对于所有设备,这种语言都适用。

roff 排版程序是整个 roff 系统的核心。 传统 roff 有两个排版程序: 对应字符设备的 nroff 和对应图形设备的 troff。通常, troff 这个名字也泛指这两种排版程序。

后处理器

设备是类似打印机、字符或图形终端等的硬件接口, 或者是用于转换为另一种字符或图形设备的软件接口。后处理器是将 troff 输出转化为一种适于某种特殊设备的格式的程序。 对于输出目标来说,roff 后处理器像是它们的设备驱动。每种设备都有为其优化的后处理器程序。后处理器解释中间输出,产生设备相关的代码,传送给设备。现在,操作系统为大多数类似打印机的硬件提供了设备驱动, 因此不必为每个打印机写一个特殊的后处理器

roff语言

roff文档是加入了roff排版元素的普通文档。roff排版语言非常强大;它几乎是一个完整的程序语言,并且提供了扩充自身的元素。使用这些元素,就可以开发为特殊程序定制的宏包。这样的宏包比普通的 roff 要容易上手得多。 所以大多数人会选择一中宏包, 不用去关心 roff 语言的内部实现。

roff文档中,请求行由.开首,用于调用命令或宏。其它行中也可用以\开始的转义序列,可以使用 ( 插入非 ASCII字符,使用 \f 改变字体,使用 " 插入行内注释,它们也可以转义特殊的控制字符像 \,用 * 转义序列获得字符串变量,用\n获取数量变量,还有很多很多其他的功能。可用的命令和转义序列见man 7 groff

宏包

宏包是一些适于以便利的办法格式化某种特殊文档的宏的集合。 它们简化了 roff 的使用。 一个包的宏定义保存在一个叫做 name.tmac 的文件中 (传统的命名是 tmac.name). 所有 tmac 文件保存在标准位置的一个或多个目录中。 有关宏包的命名和位置的细节可以看 groff_tmac文档。

文档中用到的宏包可以使用命令行选项 -m 提供给排版程序, 它们也可以在文档中指定,使用 roff 语言的 “包含文件” 命令。

著名的传统宏包有 man 用来处理传统手册页 mdoc 处理 BSD 样式的手册页;此类书籍、文档和信件的宏集合是 me (命名也许是根据它的创造者之名 Eric Allman 而来), ms (命名来自 Manuscript Macros), 还有 mm (命名来自 Memorandum Macros)。

注意事项

  • 手册页使用章节号作为文件扩展名,例如若文档的文件名后缀为.7, 也就是说它放在手册页的第 7 章
  • 传统的宏包使用包名称作为文件扩展名,例如 file.me 意思是使用了 me 宏包的文件, file.mm 使用了宏包 mm, file.ms 用的是 ms, file.pic 则是 pic 等等
  • 最好的 roff 文档编辑器是 Emacs,它提供了 nroff 模式,适于所有种类的 roff “方言”。
  • 不要在 roff 文档中包含空行或只包含空格的行, 而是使用“空行”命令 (一行中只包含一个点),或者一行注释 ." (如果需要一个构造元素的话)
  • 不要在行首用空格,因为这会导致不可预测的行为。 段落缩进可以以受控的方式,用 roff 命令构造出来
  • 每句话应当放到自己的一行中,因为句号后面的空格的处理方法是根据它结束的是短语还是句子而不同的。 要区别这两种情况,在每句话后面加上一个换行
  • 另外要使用 Emacs 的 auto-fill 模式的话,最好在每句话后面添加一个空的 roff 命令 (一行中只包含一个点)
关键词 groff troff 排版