Расстановка переносов в активных символах

Расстановка переносов в активных символах

Допустим, я хочу сделать определенные буквы активными и проделать с ними какие-нибудь хитрые вещи, например:

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

(Я знаю, что это ужасно, так как запрещает использование управляющих последовательностей, содержащих «y».)
Теперь я хотел бы, чтобы TeX все еще рассматривал перенос на «y» как обычно (полиэтилен). Я знаю, что TeX не разобьет слово, содержащее \hbox, но можно ли обмануть TeX, заставив его думать, что это просто невинная буква? Почему я думаю, что этомощьбыть возможным:

  • При поиске подходящей точки останова TeX по сути не заботится о том, активны ли символы в слове;
  • в момент появления активного символа он все еще знает, какая буква должна там быть.

Приветствуется максимально общее решение (т. е. независимо от того, что делает активный персонаж), но если это невозможно, вот что я могу заставить делать такого активного персонажа:

  • распечатать себя нормально
  • вставить себя в \hboxи \raiseэто
  • добавьте несколько кернов до или после него
  • манипулировать его внешним видом с помощью \pdfliterals

решение1

Используя LuaTeX, можно не делать буквы активными и просто манипулировать ими после расстановки переносов, как указала Ульрике Фишер в комментариях.

Ниже представлена ​​реализация этого подхода, вдохновленнаяchickenizepackage. Поскольку я впервые пишу код на 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, а подходы к параллельному взаимодействию, обсуждаемые вэтот вопроскак мне кажется, не применимы к данной ситуации:

  1. Помещение кода в сопрограмму coи ее вызов
tex.print("\\directlua{coroutine.resume(co)}")
coroutine.yield()

всякий раз, когда TeX должен выполнить какой-то макрос перед продолжением работы для 14 символов, после чего я получаю
! TeX capacity exceeded, sorry [text input levels=15]

  1. В ответе на вопрос, ссылка на который приведена выше, \loopв коде TeX используется a для многократного возобновления сопрограммы, что, очевидно, не работает, если код Lua должен использоваться в обратном вызове.

В качестве последнего средства я вижу только возможность сделать символы активными, сохранить список абзацев и временно заменить блоки, созданные активным символом, на узел глифов, содержащий исходный символ в pre_linebreak_filter, и изменить их обратно в , post_linebreak_filterиспользуя сохраненный список. Но это задача для другого дня...

Связанный контент