在活动字符处连字

在活动字符处连字

假设我想让某些字母活跃起来,并用它们做一些狡猾的事情,比如

\catcode`y=13
\defy{\leavevmode\raise.1ex\hbox{\char`y}}

(我知道这很糟糕,因为它禁止使用包含“y”的控制序列。)
现在我希望 TeX 仍然考虑在“y”处正常连字符(poly-ethy-lene)。我知道 TeX 不会破坏包含 的单词\hbox,但是否有可能欺骗 TeX 认为这只是一个无辜的字母?为什么我认为可能有可能:

  • TeX 在寻找合适的断点时并不关心单词中的字符是否处于活动状态;
  • 当活动字符出现时,它仍然知道哪个字母应该在那里。

鼓励采用尽可能通用的解决方案(即,无论主动角色做什么),但如果这不可行,我可能希望让这样的主动角色执行以下操作:

  • 打印正常
  • 将自身插入到\hbox并且\raise
  • 在前面或后面添加一些字距
  • \pdfliteral用s操纵其外观

答案1

使用 LuaTeX,可以避免使字母处于活动状态,而只需在连字后对其进行操作,正如 Ulrike Fischer 在评论中指出的那样。

以下是这种方法的实现,灵感来自于chickenize包。由于这是我第一次用 Lua 编写代码,因此欢迎提出任何建议。

transform.lua

首先,我定义一个函数,遍历列表中的字形节点并检查字符是否在名为 的表中有条目chartbl,在这种情况下,它会调用一个函数transform_char,该函数使用表中的值来操作字形节点。然后将此函数注册为post_linebreak_filter,以便在段落列表被分成几行后将其应用于段落列表(因此遵循连字模式):

transform_chars = function(head)
  for l in node.traverse_id(node.id("hhead"),head) do
    for n in node.traverse_id(node.id("glyph"),l.head) do
      chr = n.char
      if chartbl[chr] ~= nil then
        transformed = transform_char(n)
        l.head = node.insert_before(l.head,n,node.copy(transformed))
        node.remove(l.head,n)
      end
    end
  end
  return head
end

callback.register("post_linebreak_filter",transform_chars)

现在transform_char(n)可以根据具体需求进行调整。在本例中,我们在字符前后添加 kern 和 pdfliteral,并虚拟地移动字符:

transform_char = function(c)
  kbfn = node.new(node.id("kern")) -- additional kern before char
  pdfbfn = node.new(node.id("whatsit"),node.subtype("pdf_literal")) -- pdf literal before
  cn = node.new(node.id("glyph")) -- char
  cn = node.copy(c)
  pdfan = node.new(node.id("whatsit"),node.subtype("pdf_literal")) -- pdf literal after
  kan = node.new(node.id("kern")) -- additional kern after char

  tbl = chartbl[c.char]

  kbfn.kern = tex.sp(tbl["kbf"])
  pdfbfn.data = tbl["pdfbf"]
  cn.xoffset = tex.sp(tbl["xoff"])
  cn.yoffset = tex.sp(tbl["yoff"])
  pdfan.data = tbl["pdfa"]
  kan.kern = tex.sp(tbl["ka"])

  kbfn.next = pdfbfn
  pdfbfn.next = cn
  cn.next = pdfan
  pdfan.next = kan
  t = node.hpack(kbfn)
  return t
end

每个操作的值存储在chartbl

chartbl = {
  [string.byte("y")] = {
    ["kbf"] = "-0.1em",
    ["pdfbf"] = "-1 0 0 1 5.5 0 cm",
    ["xoff"] = "0ex",
    ["yoff"] = "0.5ex",
    ["pdfa"] = "-1 0 0 1 -5.5 0 cm",
    ["ka"] = "0.2em"
  }
}

例子:

\directlua{dofile("transform.lua")}
\hsize1cm
\noindent polyethylene

聚乙烯


对于文档:原则上,这种方法是一种通用解决方案,因为据我所知,TeX 可能想要对字符执行的所有操作也可以在 Lua 中完成,但函数可能会发生变化transform_char。但是,对于更复杂的任务,这似乎比让 TeX 排版更难。

所以我最初尝试做的是让post_linebreak_filter每个字符都调用一个 TeX 宏,将所需转换的结果放入寄存器中\box,然后让 Lua 用该框替换节点。

我认为这是不可能的。任何代码调用tex.print都只在 Lua 代码之后执行,并且 中讨论的并发交互方法这个问题在我看来,并不适用于这种情况:

  1. 将代码放入协程co并让其调用
tex.print("\\directlua{coroutine.resume(co)}")
coroutine.yield()

每当 TeX 应该在继续工作 14 个字符之前执行一些宏时,我就会得到
! TeX capacity exceeded, sorry [text input levels=15]

  1. 在上面链接的问题的答案中,\loopTeX 代码中使用了 a 来重复恢复协程,但如果要在回调中使用 Lua 代码,这显然不起作用。

作为最后的手段,我只看到了使字符处于活动状态的可能性,保存段落 hlist 并暂时用包含原始字符的字形节点替换活动字符构建的框pre_linebreak_filter,然后使用保存的列表将它们更改回post_linebreak_filter。但这是另一天的任务...

相关内容