
Digamos que quiero activar ciertas letras y hacer algunas cosas tortuosas con ellas, como
\catcode`y=13
\defy{\leavevmode\raise.1ex\hbox{\char`y}}
(Sé que esto es terrible porque prohíbe el uso de secuencias de control que contengan una "y".)
Ahora me gustaría que TeX todavía considere la posibilidad de separar guiones en la "y" normalmente (polietileno). Sé que TeX no dividirá una palabra que contenga una\hbox
, pero ¿es posible engañar a TeX haciéndole creer que solo hay una letra inocente? ¿Por qué lo pienso?podríaser posible:
- A TeX no le importa per se si los caracteres de una palabra están activos o no cuando busca un punto de interrupción adecuado;
- en el momento en que se produce el carácter activo, todavía sabe qué letra debería estar allí.
Se recomienda una solución lo más general posible (es decir, independientemente de lo que haga el personaje activo), pero si eso no es factible, esto es lo que me gustaría que hiciera ese personaje activo:
- imprimirse normalmente
- insertarse en un
\hbox
y\raise
- agregue algunos kerns antes o después
- manipular su apariencia con
\pdfliteral
s
Respuesta1
Usando LuaTeX, es posible evitar activar las letras y simplemente manipularlas después de la separación de palabras, como señaló Ulrike Fischer en los comentarios.
A continuación se muestra una implementación de este enfoque, inspirada en elchickenize
paquete. Dado que esta es la primera vez que escribo código en Lua, cualquier sugerencia es bienvenida.
transform.lua
Primero, defino una función que itera a través de los nodos de glifo en una lista y verifica si el carácter tiene una entrada en una tabla llamada chartbl
, en cuyo caso llama a una función transform_char
que usa los valores de la tabla para manipular el nodo de glifo. Luego, esta función se registra como post_linebreak_filter
, de modo que se aplicará a una lista de párrafos después de dividirla en líneas (por lo tanto, obedeciendo patrones de separación de palabras):
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)
Ahora transform_char(n)
se puede adaptar a las necesidades específicas. En este caso, agregamos un kern y un pdfliteral antes y después del carácter y virtualmente cambiamos el carácter:
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
Los valores para cada una de las operaciones se almacenan en 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"
}
}
Ejemplo:
\directlua{dofile("transform.lua")}
\hsize1cm
\noindent polyethylene
Para documentación: en principio, este enfoque es una solución general ya que, hasta donde tengo entendido, todo lo que TeX quiera hacer con un personaje también se puede hacer en Lua, con posibles cambios en la transform_char
función. Sin embargo, para tareas más complejas, esto parece ser más difícil de hacer que escribirlo en TeX.
Entonces, lo que inicialmente intenté hacer fue llamar post_linebreak_filter
a una macro TeX en cada carácter que coloca el resultado de la transformación deseada en un \box
registro y luego hacer que Lua reemplace el nodo por ese cuadro.
Creo que esto es imposible. Cualquier código llamado por tex.print
solo se ejecuta después del código Lua, y los enfoques para una interacción concurrente discutidos enesta preguntason, según me parece, no aplicables a esta situación:
- Poner el código en una rutina
co
y hacer que llame
tex.print("\\directlua{coroutine.resume(co)}")
coroutine.yield()
cada vez que TeX debe ejecutar alguna macro antes de continuar funciona para 14 caracteres, después de lo cual aparece
! TeX capacity exceeded, sorry [text input levels=15]
- En la respuesta a la pregunta vinculada anteriormente,
\loop
se usa a en el código TeX para reanudar repetidamente la rutina, lo que obviamente no funciona si el código Lua se va a usar en una devolución de llamada.
Como último recurso, solo veo la posibilidad de activar los caracteres, guardar la lista de párrafos y reemplazar temporalmente los cuadros creados por el personaje activo con un nodo de glifo que contenga el carácter original en pre_linebreak_filter
y volver a cambiarlos post_linebreak_filter
usando la lista guardada. . Pero eso es tarea para otro día...