乐趣区

关于tex:LuainConTeXt06伪竖排

上一篇:工夫戳

这次会用到 Lua,我保障。

页码

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

\setuppagenumbering[location={footer,inmargin}]

对此却无能为力。既然如此,还要它作甚,破而后立吧。先将上述代码批改为

\setuppagenumbering[location=] % 敞开页码 

而后应用 \setupfootertexts 在页脚的留白区域安放页码:

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

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

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

当初将以下两行代码增加到 card-env.tex 里:

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

工夫戳

我想让工夫戳呈现在版心(或注释区域)右侧的留白区域,\setuptexttexts 可成就此事:

\setuptexttexts[margin][foo][bar]

可在注释区域的左侧和右侧的留白区域居中搁置 foobar

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

去掉 foo,将 bar 换成工夫戳:

\setuptexttexts
  [margin]
  [][\hfill 2023 年 01 月 26 日 凌晨 4 时 44 分 \hfill]

后果是工夫戳文字大部分出界了。这在预料之中,留白区域太窄,工夫戳太长。

应用 \rotate 能够依据指定角度逆时针旋转文本,

\setuptexttexts
  [margin]
  [][\hfill\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}\hfill]

在生成 PDF 文件的过程中,上述代码会导致 context 命令报错:

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

context 命令(庄重地说是 TeX 引擎)无奈了解我在 \setuptexttexts 里传入的信息是什么,反而认为我传入的是谬误的信息。能够用 {} 结构一个编组(Group),将 \rotate[...]{...} 语句囊括于其中,从而让 context 命令认为传入 \setuptexttexts 的是一段挺失常的文本:

\setuptexttexts
  [margin]
  [][\hfill{\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}}\hfill]

当初已将工夫戳残缺地浮现于版心右侧的留白区域,但其中每个字是躺着的,须要设法让其中的汉字站立起来。

在进行下文之前,须要给出上述最终排版后果对应的 card-env.tex 和 card.tex。

card.tex:

\environment card-env
\setuptexttexts
  [margin]
  [][\hfill{\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}}\hfill]
\showframe
  
\starttext
看版心右侧 $\rightarrow$
\stoptext

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]

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

% 页码
\setuppagenumbering[location=]
\setupfootertexts[margin][][\hfill\pagenumber\hfill]

% 题目
\setuphead[title][align=middle]

TeX 宏

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

\setuptexttexts
  [margin]
  []
  [\hfill{\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}}\hfill]

能够定义一个宏,用于简化 \setuptexttexts 语句。例如

\def\timestamp{\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}}
\setuptexttexts
  [margin]
  [][\hfill\timestamp\hfill]

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

最简略的宏定义,模式如下

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

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

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

宏能够承受参数,例如:

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

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

\ 宏 {参数}

例如,

\timestamp{2023 年 01 月 26 日 凌晨 4 时 44 分}

开展后果为:

\rotate[rotation=270]{2023 年 01 月 26 日 凌晨 4 时 44 分}

仅须要晓得这些常识,便可进入 Lua 的世界。

Hello,Lua!

我要用 Lua 语言对 \timestamp 所承受的参数里的每个汉字逆时针旋转 90 度角。对于 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]{字}

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
向右看 $\rightarrow$
\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.ne…

文档里说:

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。

站立起来的这几个汉字,其间距过于严密,可通过 TeX 命令 \kern 结构指定宽度的空白空间予以调解:

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

\ctxlua 尽管能解决问题,然而让 \timestamp 宏的定义甚为俊俏。假使在 \startluacode\stopluacode 里定义一个 rotate 函数,便可让 \timestamp 的定义大幅简化。

试试看,

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

我试过了,没问题,仍然能生成上一节最初的排版后果。顺便解释一下,my 是我为 rotate 的命名空间。应用命名空间的益处是,可防止函数同名而造成一些误会。

竖排的工夫戳

好了,当初咱们能够再对一下 card.tex 了。

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

\starttext
向右看 $\rightarrow$
\stoptext

后果很丑:

如果仅仅让汉字竖立,其它符号放弃躺平,后果会好看一些。要实现该想法,须要对 \timestamp 的参数里的汉字进行辨认。上面定义一个函数,使之可能基于汉字的 Unicode 码区辨认汉字:

function my.is_cjk_char(c)
    if c >= 0x3400 and c <= 0x4db5
        or c >= 0x4e00 and c <= 0x9fa5
        or c >= 0x9fa6 and c <= 0x9fbb
        or c >= 0xf900 and c <= 0xfa2d
        or c >= 0xfa30 and c <= 0xfa6a
        or c >= 0xfa70 and c <= 0xfad9
        or c >= 0x20000 and c <= 0x2a6d6
        or c >= 0x2f800 and c <= 0x2fa1d
        or c >= 0xff00 and c <= 0xffef
        or c >= 0x2e80 and c <= 0x2eff
        or c >= 0x3000 and c <= 0x303f
        or c >= 0x31c0 and c <= 0x31ef then
        return true;
    else
        return false;
    end
end

批改 my.rotate 函数:

function my.rotate(x, a)
    for _, c in utf8.codes(x) do
        if my.is_cjk_char(c) then
            context("{\\rotate[rotation=%d]{%s}}\\kern.25em", a, utf8.char(c))
        else
            context(utf8.char(c))
        end
    end
end

后果如下:

该后果仍然不尽人意,数字和汉字没有竖直居中对齐。若要解决这个问题,须要整些暴力手段(心愿当前能找到更为简略的办法):

function my.rotate(x, a)
    for _, c in utf8.codes(x) do
        if my.is_cjk_char(c) then
            context("{\\rotate[rotation=%d]{%s}}\\kern.25em",
                    a, utf8.char(c))
        else
            context("{\\raise.5\\maxdepth\\hbox{%s}}", utf8.char(c))
        end
    end
end

TeX 宏 \hbox 可将字符突围在一个程度对齐的盒子里。在 card.tex 文件里,\raise.5\maxdepth\hbox{...} 可将竖排的每个非 CJK 字符向右偏置 0.5 倍的 \maxdepth

TeX 宏 \raise\lower 可将字符向上(或向下)晋升指定间隔,然而将其与 ConTeXt 宏 \rotate 宏在竖排时配合应用时,用处便会变为向左或向右微调字符地位。

\maxdepth 是 ConTeXt 排版时为一行文字定义的最大深度值,其含意可参考:

残缺的 card.tex 内容如下:

\environment card-env
\startluacode
my = {}
function my.is_cjk_char(c)
    if c >= 0x3400 and c <= 0x4db5
        or c >= 0x4e00 and c <= 0x9fa5
        or c >= 0x9fa6 and c <= 0x9fbb
        or c >= 0xf900 and c <= 0xfa2d
        or c >= 0xfa30 and c <= 0xfa6a
        or c >= 0xfa70 and c <= 0xfad9
        or c >= 0x20000 and c <= 0x2a6d6
        or c >= 0x2f800 and c <= 0x2fa1d
        or c >= 0xff00 and c <= 0xffef
        or c >= 0x2e80 and c <= 0x2eff
        or c >= 0x3000 and c <= 0x303f
        or c >= 0x31c0 and c <= 0x31ef then
        return true;
    else
        return false;
    end
end
function my.rotate(x, a)
    for _, c in utf8.codes(x) do
        if my.is_cjk_char(c) then
            context("{\\rotate[rotation=%d]{%s}}\\kern.25em",
                    a, utf8.char(c))
        else
            context("{\\raise.5\\maxdepth\\hbox{%s}}", utf8.char(c))
        end
    end
end
\stopluacode
\def\timestamp#1{\rotate[rotation=270]{\ctxlua{my.rotate("#1", 90)}}}
\setuptexttexts
  [margin]
  [][\hfill{\timestamp{2023 年 01 月 26 日 凌晨 04 时 44 分}}\hfill]
\showframe

\starttext
向右看 $\rightarrow$
\stoptext

可将 \environment card-env\showframe 之间的内容挪动到 card-env.tex 文件。

参考

  • https://wiki.contextgarden.ne…
  • https://wiki.contextgarden.ne…
  • 汉字的 Unicode 范畴
  • TeX 盒子
退出移动版