Flashkard 打印输出

作者:Phil Hughes

Hal Stanton 的 关于 FlashKard 的文章 使我放弃了我一直在开发的简陋的抽认卡软件。我现在想要的是一种将数据打印到抽认卡上的方法。我本打算编写一个典型的黑客程序——很可能使用 awk 和 troff——来打印卡片,但我决定尝试使用 XML。

我所说的使用 XML 是指我决定使用标准的 XML 工具来格式化数据以进行输出。我以前从未这样做过,但我知道 XSLT 是一个重要的流行语。XSLT 代表 XML 样式表语言转换,它是一种旨在定义从 XML 到其他格式的转换的语言。所以,我开始阅读。通常,XSLT 用于将 XML 转换为 HTML,但是对你可以用它做什么没有任何限制。所以我决定在我的抽认卡项目中尝试一下。

在决定使用 XSLT 之后,在我可以继续之前,我仍然需要另外两条信息。首先,我必须决定将 XML 转换为什么。其次,由于涉及到一些程序逻辑来将卡片放置在页面上的正确位置,我需要决定一种通用的编程语言。在考虑了各种编程语言的替代方案——其中 Python 听起来是最好的——之后,我意识到如果我只是生成 PostScript,我可以让打印机本身来处理放置问题。这似乎很奇怪,但我想“为什么不呢?”。

输出格式

选择了 PostScript 后,我坐下来决定如何在页面上放置卡片。抽认卡需要是双面的。起初,我考虑打印一面,然后将卡片纸放回打印机以打印另一面。然而,这是一个逻辑上的噩梦,因为很容易将纸张错误地放入打印机,出现套准问题或因打印机卡纸而错序。

我决定采用另一种方法,该方法涉及另一种高科技设备,称为“胶棒”。这个想法是将每张卡片的正面和背面都打印在一页的正面,然后将其对折,粘合在一起并切割成实际的卡片。双层纸和胶水使卡片足够重,可以工作而不会散开。

现在,是时候坦白了:这不是一个漂亮、完成的生产系统。然而,它是一个可行的东西和一个概念验证。对于生产环境,重要的是在配置文件中定义卡片尺寸和字体。此外,目前每面的消息都打印在单行中,而没有考虑尺寸。需要实现行折叠。

好的,回到工作。我选择了 1.5 英寸 x 2.5 英寸的卡片尺寸,这使得在一面 Letter 尺寸的纸张上可以获得九张卡片——包括正面和背面。我设置了 1 英寸的顶部和底部边距以及 0.5 英寸的左右边距。为了使折叠和切割更容易,我想在页面中间打印一条折叠线——在正面和背面之间——以及卡片边缘的切割标记。通过这种折叠,背面上的打印与正面上的打印是颠倒的。在考虑了一分钟后,我决定这并不重要——它只是定义了使用卡片时翻转卡片的方式。

PostScript

所有内容,即 PostScript 和 XSL,都在一个文件中,您可以从这里下载。您现在可以忽略 XML 内容。请注意,如果您尝试在浏览器中显示它,由于 XML 的原因,它不会正确显示。您可以在下面看到示例输出。

Flashkard Printed Output

如果您从未使用过 PostScript,请做好准备。PostScript 是一种 RPN(逆波兰表示法)语言。如果您曾经使用过 HP 计算器,您就会明白我的意思。如果不是,快速的解释是,您通过将项目放在堆栈上,然后在该堆栈上操作来完成事情。例如,要添加两个数字,您将数字放在堆栈上,然后执行 add 运算符。然后,运算符获取数字,将它们相加,并将结果放回堆栈。注意:我讨厌 RPN 语言。

撇开免责声明不谈,PostScript 实际上是一种非常干净的语言,对于完成我们需要做的工作来说还不错。使用 PostScript 的方式是描述您想要放在页面上的所有内容——字符、线条、填充区域等等——然后告诉它打印页面。这意味着我们不必记住很多东西,然后按顺序向下处理页面;我们只需四处移动并将我们想要的东西放在页面上。

在 PostScript 中,长度的基本单位是 1/72 英寸。就我个人而言,我对使用这样的单位不是很兴奋,所以我定义了一个名为 inch 的函数,该函数获取堆栈上的当前值,将其乘以 72,然后将该值放回堆栈。

/inch { 72 mul } def

这样,我在一个数字后添加单词 inch,它就会乘以 72。

如果您查看 cutmarks 函数,您可以看到一大堆movetolineto语句。正如您可能期望的那样,这些运算符从堆栈中取出两个值——一个 x 和一个 y 坐标,其中 0,0 是页面的左下角,正向移动是向右或向上——并且要么将当前位置移动到指定的坐标,要么从当前位置到指定位置绘制一条线。

转到startit函数,您可以看到页面的所有设置工作。我定义了三个九元素数组——x、yf 和 yb——它们包含放置九张卡片中文本的 x 和 y 坐标(yf 代表正面,yb 代表背面)。(请注意,PostScript 中的数组索引从 0 开始。)另外两个初始化步骤是定义用于文本的字体和字体大小,并设置卡片编号计数器cardno为 0。

定义了另外两个实用函数,cardsteppageout. pageout 检查当前卡片编号。如果它大于 0,pageout 会绘制 cutmarks——通过调用cutmarks函数——然后使用showpage内置函数来打印页面。cardstep 递增卡片计数器。然后,如果计数器大于 8,cardstep 调用 pageout 来打印页面。然后,它将 cardno 重置为 0,为下一页做准备。

最后两个函数是frontback. 它们通过索引到位置数组来移动到页面上的正确位置。然后,它们使用show内置函数来打印堆栈顶的值。back 函数调用 cardstep 以移动到下一个位置。因此,以下两行将打印一张卡片

(Front Side) front
(Back Side) back

我说的是两行,但空格在 PostScript 中并不重要。如果此信息在一行上,您将获得相同的结果。括号用于描绘放置在堆栈上的字符串。

所有以斜杠 (/) 开头的行都具有刚定义的函数。真正的程序从行开始startit,它调用 startit 初始化函数。接下来,必须输入一系列对 front 和 back 的调用,然后调用 pageout 以输出最后一页,如果上面有任何卡片的话。

XSL

我用一些示例数据测试了 PostScript 部分,它工作正常。所以,接下来是下一步,即将 FlashKard 中的 XML 转换为驱动 PostScript 代码所需的内容。这里需要两个部分。首先,我需要我必须编写的 XSL。其次,我需要一个程序来读取 FlashKard 中的 XSL 和 XML 文件,然后输出 PostScript 以发送到打印机。

容易的部分是找到程序;xsltproc 正是这个程序。完成了一件事。现在是时候用一种我以前从未见过的语言编写一些东西了。但是,它会比用 RPN 语言编写更糟糕吗?

事实证明,实际上没有太多事情要做。在一些 XSL 样板代码之后 (<xsl:stylesheet ...>),我需要将输出格式定义为 text,因为 HTML 是默认格式。text 的意思是“任何其他格式”。这是通过以下方式完成的

<xsl:output method="text">

我要输出的第一件事是 PostScript 程序本身。这是通过在<xsl:template match="/">标签之后立即包含它来完成的。/ 的匹配匹配整个 XML,因此它在文件开头被处理。请注意,我已将%!PS与 XSL 标签放在同一行。这是必要的,以便打印机可以将此视为数据第一行的开头。否则,打印后台处理程序会认为这是更多文本,并打印而不是解释 PostScript。

在匹配的</xsl:template>标签之前,还有另一个 XSL 标签,即<xsl:apply-templates/>. 这告诉 xsltproc 任何其他匹配的模板都将在此处应用。

另一个模板的匹配表达式为match="e". 这匹配描述单个卡片的块,并且在 FlashKard 文章的注释中对此进行了解释。在该块中有一个o块用于原始语言条目和一个t块用于翻译。使用value-of功能,我抓取这些值,将它们放在括号中,并在它们后面加上 front 或 back。

就是这样,伙计们。假设 XSL 在 ks.xsl 中,输入命令

xsltproc ks.xsl creatures.kvtml | lpr

即可获得您的第一组抽认卡。

正如我之前提到的,这是一个概念验证。通用化 PostScript,处理行折叠以及为此命令行编写 shell 脚本包装器将使事情变得简洁并创建一个有用的程序。

版权 (c) 2004, Phil Hughes。最初发表于 Linux Gazette 第 98 期。版权 (c) 2004, Specialized Systems Consultants, Inc.

SSC 集团出版人 Phil Hughes 偶尔喜欢亲力亲为。但是,您不会发现他驾驶带有自动变速器的汽车或使用 Emacs。

加载 Disqus 评论