
Допустим, я хочу сделать определенные буквы активными и проделать с ними какие-нибудь хитрые вещи, например:
\catcode`y=13
\defy{\leavevmode\raise.1ex\hbox{\char`y}}
(Я знаю, что это ужасно, так как запрещает использование управляющих последовательностей, содержащих «y».)
Теперь я хотел бы, чтобы TeX все еще рассматривал перенос на «y» как обычно (полиэтилен). Я знаю, что TeX не разобьет слово, содержащее \hbox
, но можно ли обмануть TeX, заставив его думать, что это просто невинная буква? Почему я думаю, что этомощьбыть возможным:
- При поиске подходящей точки останова TeX по сути не заботится о том, активны ли символы в слове;
- в момент появления активного символа он все еще знает, какая буква должна там быть.
Приветствуется максимально общее решение (т. е. независимо от того, что делает активный персонаж), но если это невозможно, вот что я могу заставить делать такого активного персонажа:
- распечатать себя нормально
- вставить себя в
\hbox
и\raise
это - добавьте несколько кернов до или после него
- манипулировать его внешним видом с помощью
\pdfliteral
s
решение1
Используя LuaTeX, можно не делать буквы активными и просто манипулировать ими после расстановки переносов, как указала Ульрике Фишер в комментариях.
Ниже представлена реализация этого подхода, вдохновленнаяchickenize
package. Поскольку я впервые пишу код на 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)
можно адаптировать под конкретные нужды. В этом случае мы добавляем керн и 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, а подходы к параллельному взаимодействию, обсуждаемые вэтот вопроскак мне кажется, не применимы к данной ситуации:
- Помещение кода в сопрограмму
co
и ее вызов
tex.print("\\directlua{coroutine.resume(co)}")
coroutine.yield()
всякий раз, когда TeX должен выполнить какой-то макрос перед продолжением работы для 14 символов, после чего я получаю
! TeX capacity exceeded, sorry [text input levels=15]
- В ответе на вопрос, ссылка на который приведена выше,
\loop
в коде TeX используется a для многократного возобновления сопрограммы, что, очевидно, не работает, если код Lua должен использоваться в обратном вызове.
В качестве последнего средства я вижу только возможность сделать символы активными, сохранить список абзацев и временно заменить блоки, созданные активным символом, на узел глифов, содержащий исходный символ в pre_linebreak_filter
, и изменить их обратно в , post_linebreak_filter
используя сохраненный список. Но это задача для другого дня...