美文网首页
《Lua in ConTeXt》06:Hello,Lua!

《Lua in ConTeXt》06:Hello,Lua!

作者: 明夷 | 来源:发表于2021-04-30 18:26 被阅读0次

这次会用到 Lua,我保证。

在任何重要的行动在开始前,参与者需要对表,统一时钟,从而实现任务的同步。我们需要对一下 card-env.tex:

\definepapersize[card][width=85.6mm,height=53.98mm]
\setuppapersize[card]
\setuplayout
  [backspace=.1\paperwidth,
    width=.8\paperwidth,
    topspace=.015\paperheight,
    height=.97\paperheight,
    leftmargin=.666\backspace,
    rightmargin=.666\cutspace,
    headerdistance=.025\makeupheight,
    footerdistance=.025\makeupheight,
    textheight=.95\makeupheight]
\setuppagenumbering[location={footer,inmargin}]

\setuphead[title][align=middle]

\definefontfamily[myfont][serif][sourcehanserifcn]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]

然后再对一下 card.tex:

\environment card-env
\setupfootertexts
  [\hfill 2021 年 04 月 29 日 下午 6 时 30 分\hfill][]
\showframe
\starttext
……Hello,汉字!……
\stoptext

以及 card.tex 对应的排版结果 card.pdf:

页码

在我的浅薄的审美范畴,card.pdf 的页码没有在页脚(footer)的留白(Margin)区域居中,甚为不美。然而,card-env.tex 里的

\setuppagenumbering[location={footer,inmargin}]

对此却无能为力。既然如此,还要它作甚,破而后立吧。

先去掉页码:

\setuppagenumbering[location=]

于是页码就不见了……

使用 \setupfootertexts 能够在页脚的留白区域正中安放页码,还记得在设置时间戳的时候使用的 \hfill 大法吗?现在,再用一次

\setupfootertexts[margin][][\hfill 1\hfill]

页码 1 就被安放在页脚右侧留白区域的正中了,如下图所示:

当然不可能所有页面的页码都为 1,所以应当使用 \pagenumber 获得每一页对应的页码:

\setupfootertexts[margin][][\hfill\pagenumber\hfill]

现在将以下两行代码添加到 card-env.tex 里:

\setuppagenumbering[location=]
\setupfootertexts[margin][][\hfill\pagenumber\hfill]

时间戳

时间戳放在页脚,也不是很合理。我觉得让它出现在版心(或正文区域)右侧的留白区域会更好。\setuptexttexts 可成就此事,例如

\setuptexttexts[margin][foo][bar]

可在正文区域的左侧和右侧的留白区域放置 foobar

也许 ConTeXt 世界前世跟位置居中这件事有仇,总之它是不肯轻易就让文本在指定区域居中了。再用一次 \hfill 大法:

\setuptexttexts[margin][\hfill foo\hfill][\hfill bar\hfill]

又一次成功了,如下图所示:

去掉 foo,将 bar 换成时间戳:

\setuptexttexts
  [margin]
  []
  [\hfill 2021 年 04 月 29 日 下午 6 时 30 分\hfill]

结果如下图所示,时间戳文字大部分出界了……这应该是预料之中的事,留白区域太窄,时间戳太长。

让时间戳的文字竖向排放即可。使用 \rotate 可以根据指定角度逆时针旋转文本,例如

\environment card-env
\starttext
\hfill\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}
\stoptext

排版结果如下:

但是,倘若将上述示例的 \rotate 语句放在 \setuptexttexts 命令里,即

\setuptexttexts
  [margin]
  []
  [\hfill\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}\hfill]

在生成 PDF 文件的过程中,TeX 编译器会报错:

tex error       > tex error on line 1 in file ./card.tex: Argument of \rotate has an extra }

TeX 编译器有时候会莫名其妙地很严厉,其实,原本是它自身能力不足,出现了事故,却喜欢归咎于用户,而且给出的错误信息,也非常模糊。它给出的这个错误,是因为它不能理解我在 \setuptexttexts 里传入的信息是什么,反而认为我传入的是错误的信息。要让它不生气,就要用 {} 构造一个编组(Group),让 TeX 编译器认为我给 \setuptexttexts 传入的是一段挺正常的文本,做法如下:

\setuptexttexts
  [margin]
  []
  [\hfill\{rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}}\hfill]

结果就是时间戳差一点就很完美地被我放置到了正文区域右侧的留白区域了,如下图所示:

之所以是差一点完美,是因为所实现的并非真正的文字竖排,因为时间戳里的每个字是平躺着的。但是,不要追求完美!否则,你会发现,阿拉伯数字立起来后,会多么丑陋。

为了展现丑陋,我需要用 Lua。但是,在进行下文之前,我们需要对一下 card-env.tex 和 card.tex。

card-env.tex:

\definepapersize[card][width=85.6mm,height=53.98mm]
\setuppapersize[card]
\setuplayout
  [backspace=.1\paperwidth,
    width=.8\paperwidth,
    topspace=.015\paperheight,
    height=.97\paperheight,
    leftmargin=.666\backspace,
    rightmargin=.666\cutspace,
    headerdistance=.025\makeupheight,
    footerdistance=.025\makeupheight,
    textheight=.95\makeupheight]
 
\setuppagenumbering[location=]
\setupfootertexts[margin][][\hfill\pagenumber\hfill]

\setuphead[title][align=middle]

\definefontfamily[myfont][serif][sourcehanserifcn]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]

card.tex:

\environment card-env
\setuptexttexts
  [margin]
  []
  [\hfill{\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}}\hfill]
\starttext
向右看……
\stoptext

以及排版结果:

TeX 宏

接下来,焦点是 card.tex 源文件里的

\setuptexttexts
  [margin]
  []
  [\hfill{\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}}\hfill]

我们可以定义一个控制序列,宏,或者命令……这三个术语可以理解成一回事……用它去简化 \setuptexttexts 语句。例如

\def\timestamp{\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}}
\setuptexttexts
  [margin]
  []
  [\hfill\timestamp\hfill]

我试验过了,排版结果依然与上一节最后给出的排版结果相同。\timestamp 是一个宏。它在 \stemptexttexts 语句里出现的时候,可称为它被调用了。宏调用的结果便是它的定义。

最简单的宏定义,形式如下

\def\foo{...宏定义...}

宏又了定义之后,TeX 遇到 \foo,就会用它的定义替换它,这个过程称为宏的展开。例如

\environment card-env
\starttext
\def\hello{Hello,汉字!} % 宏定义
\hello % 宏调用,\hello 会被 TeX 编译器替换为 Hello,汉字!
\stoptext

宏可以接受参数,例如:

\def\timestamp#1{\rotate[rotation=270]{#1}}

其中 #1 表示 \timestamp 的第一个参数。带参数的宏,用法通常是

\宏名{参数}

例如,

\timestamp{2021 年 04 月 29 日 下午 6 时 30 分}

展开结果为:

\rotate[rotation=270]{2021 年 04 月 29 日 下午 6 时 30 分}

仅需要知道这些知识,便可进入 Lua 的世界。

Hello,Lua!

我要用 Lua 语言对 \timestamp 所接受的参数里的每个字符逆时针旋转 90 度角,如此它们都可以向 ConTeXt 世界宣告,它们站起来了!

对于 Lua 语言,完成此事的关键在于遍历一个字符串里的每个字符,稍微有些难度的是,这个字符串里含有汉字,这需要 Lua 支持文字的 UTF-8 编码。不需要解释太多,汉字虽然在计算机软件技术里也是疼痛了很久,但现在是 UTF-8 的时代。

假设有 Lua 字符串变量 x

x = "我喜欢汉字!"

Lua 语言已经不需要我们再为它做什么额外的工作,它能够理解 UTF-8。可遍历 x 的每个字符的代码如下:

for _, c in utf8.codes(x) do
    print(utf8.char(c))
end

变量 c 的值是 Unicode 码位(codepoint),需要使用 Lua 语言的 utf8 库提供的 utf8.char 函数将其转换为 UTF-8 编码,然后方能被 print 之类的函数视为字符串而输出至程序外部——终端或文本文件。 之所以从 UTF-8 编码的字符串里获得 Unicode 码位,再将 Unicode 码位转化为 UTF-8 编码,字符串里每个字符的编码长度并不固定,先将字符串转化为固定长度的 Unicode 码位序列,对字符串的解析会更方便。

倘若系统里并未安装 Lua 解释器,没有关系,因为 ConTeXt 的 TeX 解释器里内嵌了 Lua 解释器,因此可将上述 Lua 代码存入 .lua 文件,例如 foo.lua,然后在在终端执行以下命令:

$ context --noconsole foo.lua

可得到以下输出:

resolvers  ... ... ...
我
喜
欢
汉
字
!
system          | total runtime: 0.489 seconds of 0.539 seconds

虽然 context 命令输出了很多它觉得有必要输出的信息,但是也输出了我想看到的信息。

要旋转字符串里的每个字符,只需对上述的字符串遍历代码略作修改,例如

for _, c in utf8.codes(x) do
    print(string.format("\\rotate[rotation=90]{%s}", utf8.char(c)))
end

可在终端输出

\rotate[rotation=90]{我}
\rotate[rotation=90]{喜}
\rotate[rotation=90]{欢}
\rotate[rotation=90]{汉}
\rotate[rotation=90]{字}
\rotate[rotation=90]{!}

string.format 是 Lua 的字符串格式化函数,在上述代码里,它可将 utf8.char(c) 生成的汉字信息作为字符串嵌入

"\\rotate[rotation=90]{%s}"

中的 %s 位置,并取代 %s。这就是所谓的字符串格式化。不使用字符串格式化函数也能产生与上述代码等价的输出,只需使用字符串连接符号 ..,例如:

print("\\rotate[rotation=90]{" .. utf8.char(c) .. "}"))

至于上述代码里,为何输出 \\rotate... 需要用两个反斜线符号 \,因为在 Lua 语言里,\ 符号用于对一些特殊符号进行转义,而 \ 自身也是此类特殊符号。

至此,关键技术已然解决,但如何将上述的 Lua 代码嵌入 ConTeXt 源文件呢?可使用 \ctxlua 啊!

\ctxlua

\ctxlua 说,看我的!

\def\timestamp#1{\rotate[rotation=270]{\ctxlua{%
  for _, c in utf8.codes(x) do
    context("\\rotate[rotation=90]{%s}", utf8.char(c))
  end}}

将上一节给出的字符串遍历代码嵌入 \timestamp 的定义之后,变动的仅仅是将 Lua 函数 print 替换为函数 context,因为后者可将信息输出到 PDF 文件里,而前者仅能将信息输出到终端。此外,string.format 也不需要了,因为 context 函数自身支持字符串格式化。

现在,对一下 card.tex 吧……

\environment card-env
\def\timestamp#1{\rotate[rotation=270]{\ctxlua{%
  x = "#1"
  for _, c in utf8.codes(x) do
    context("\\rotate[rotation=90]{%s}", utf8.char(c))
  end}}
\setuptexttexts
  [margin]
  []
  [\hfill\timestamp{我喜欢汉字!}\hfill]

\starttext
向右看……
\stoptext

ConTeXt 的 TeX 编译器(LuaTeX)在处理 card.tex 时,会报错:

tex error       > tex error on line 13 in file ./card.tex: The file ended when scanning a definition.

这个错误让我一整天徘徊不前,且百思不得其解。直到我去 ConTeXt 的 Wiki 上查阅了 \ctxlua 的文档:

https://wiki.contextgarden.net/Command/ctxlua

文档里说:

Use this command to quickly execute some Lua code. TeX expands the argument before Lua receives it. Advantage: you can pass the contents of macro parameters like #1 to Lua. Disadvantage: everything after a percent sign is ignored, and once the comments are processed out the linebreaks are stripped, too.

意思时,字符串格式化里的 % 被 TeX 编译器误以为是 TeX 源文件里的注释符 %。这个问题几乎无解。此时,可用字符串连接符 .. 代替字符串格式化:

\def\timestamp#1{\rotate[rotation=270]{\ctxlua{%
  x = "#1"
  for _, c in utf8.codes(x) do
    context("\\rotate[rotation=90]{" .. utf8.char(c) .. "}")
  end}}}

经过上述修正,card.tex 可通过 TeX 编译器,顺利转化为 card.pdf,结果如下图所示:

我觉得站立起来的这几个汉字的字距过于紧密,而且那个叹号也很不协调。字距问题,可通过 \kern 予以调节,这个 TeX 命令可生成指定宽度的空间。至于叹号难看,干脆不要它了。因此,对上述代码继续进行修改:

\def\timestamp#1{\rotate[rotation=270]{\ctxlua{%
  x = "#1"
  for _, c in utf8.codes(x) do
    context("\\kern.1em\\rotate[rotation=90]{" .. utf8.char(c) .. "}\\kern.1em")
  end}}}

结果见下图:

定义一个 Lua 函数

\ctxlua 虽然能解决问题,但是让 \timestamp 宏的定义甚为丑陋。倘若在 \startluacode ... \stopluacode 里定义一个 rotate 函数,便可让 \timestamp 的定义大幅简化。

试试看,

\startluacode
my = {}
function my.rotate(x, a)
    for _, c in utf8.codes(x) do
        context("\\kern.1em\\rotate[rotation=%d]{%s}\\kern.1em",
                a,
                utf8.char(c))
    end
end
\stopluacode
\def\timestamp#1{\rotate[rotation=270]{\ctxlua{my.rotate("#1", 90)}}}

我试过了,没问题,依然能生成上一节最后的排版结果。顺便解释一下,my 是我为 rotate 的命名空间。使用命名空间的好处是,可避免函数同名而造成一些误会。my 的意思不是“我的”,而是“明夷”的汉语拼音首字母组合。

竖排的时间戳

好了,现在我们可以再对一下 card.tex 了。

\environment card-env
\startluacode
my = {}
function my.rotate(x, a)
    for _, c in utf8.codes(x) do
        context("\\kern.1em\\rotate[rotation=%d]{%s}\\kern.1em",
                a,
                utf8.char(c))
    end
end
\stopluacode
\def\timestamp#1{\rotate[rotation=270]{\ctxlua{my.rotate("#1", 90)}}}
\setuptexttexts
  [margin]
  []
  [\hfill{\timestamp{2021 年 04 月 29 日 下午 6 时 30 分}}\hfill]

\starttext
向右看……
\stoptext

对应的 card.pdf 如下图所示:

是不是很丑?

结语

我想了好多天,该如何在这份文档里第一次认真地引入 Lua 代码,结果也很有启发性……汉字的优点是支持竖排。

相关文章

  • 《Lua in ConTeXt》06:Hello,Lua!

    这次会用到 Lua,我保证。 在任何重要的行动在开始前,参与者需要对表,统一时钟,从而实现任务的同步。我们需要对一...

  • 《Lua in ConTeXt》01:Hello world!

    释名 ConTeXt,我不厌其烦地打出它的大小写字母,它的意思即不是「上下文」,也不是「语境」或「环境」,而是 T...

  • 《Lua in ConTeXt》05:Hello,汉字!

    我要在卡片的页脚区域为卡片增加时间戳,例如, 然而,迄今为止,我还没介绍如何让 ConTeXt 支持汉字。 TeX...

  • Mac平台中编译安装Lua运行环境

    这篇文章主要介绍了Mac平台中编译安装Lua运行环境及Hello Lua实例,本文给出了两种Hello Lua示例...

  • 《Lua in ConTeXt》10:学一点 Lua

    沉缅于 ConTeXt 有些不能自拔,几乎忘记了这份文档的题目是 ConTeXt 里的 Lua。主角应该是 Lua...

  • sublime text3 编译 lua

    首先打开sublime,写一句lua代码 print("hello") 保存成hello.lua 在tools->...

  • lua开篇-hello lua

    1、获取lua 访问lua官网:http://www.lua.org/ 下载最新的lua版本lua-5.3.4.t...

  • 2018-08-01

    Lua 版 hello world! 'print' 是lua中的输出语句 上面可以看到 '--' 符号是单行注释...

  • Hello Lua!

    起航# 接触lua也是很长时间了,总感觉要写点什么,但是一直也不能下决心写下去,今天把lua的源码编译了一遍,得到...

  • lua学习笔记(一)

    永远的hello,world !/usr/bin/lua print("Hello World") --单行注释 ...

网友评论

      本文标题:《Lua in ConTeXt》06:Hello,Lua!

      本文链接:https://www.haomeiwen.com/subject/xbdnrltx.html