LuaTeX / LuaJITTeX

lualatex no es LuaTeX. Siga el enlace simbólico en TeX Live 2024 y verá que no apunta a luatex sino a luahbtex, un binario distinto, con HarfBuzz incorporado. fmtutil.cnf dice lo mismo: lualatex luahbtex ... lualatex.ini, de modo que hasta el formato lo genera LuaHBTeX. Al ejecutarlo, el encabezado se presenta como This is LuaHBTeX, Version 1.18.0 (TeX Live 2024), algo que se lee sin reparar en ello. Partiendo de qué es realmente el motor que usa LaTeX, esta página recorre por dónde entra Lua en la composición (\directlua y los callbacks), en qué se ha convertido LuaJITTeX y cuánto sobrevive, medido de verdad, de la fama de lentitud de LuaLaTeX.

lualatex es LuaHBTeX: qué añade HarfBuzz

Lleva HarfBuzz entero dentro del binario. LuaHBTeX entró en TeX Live en 2020, y fue entonces cuando se sustituyó lo que hay detrás de lualatex; las notas de aquella versión dejaban claro que LuaTeX seguía siendo LuaTeX corriente y que solo cambiaba el formato de LaTeX. Una línea zanja el asunto: compile \directlua{tex.print(status.luatex_engine)} con lualatex y la página imprime luahbtex; con luatex, imprime luatex. Pruebe require("luaharfbuzz"): funciona bajo luahbtex, que informa de HarfBuzz 8.3.0, mientras que el luatex desnudo falla con module 'luaharfbuzz' not found. HarfBuzz no es un fichero situado en algún lugar del disco: está enlazado estáticamente en el ejecutable, y por eso ningún paquete puede añadirlo después, y por eso se distribuye como binario aparte.

shell
$ readlink $(which lualatex)
luahbtex

$ grep -E '^lualatex ' $(kpsewhich fmtutil.cnf)
lualatex luahbtex language.dat,language.dat.lua lualatex.ini

$ lualatex --version | head -1
This is LuaHBTeX, Version 1.18.0 (TeX Live 2024)

# Ask the engine what it is, from inside a document:
%   \directlua{tex.print(status.luatex_engine)}     -> luahbtex
%   \directlua{tex.print(_VERSION)}                 -> Lua 5.3
%   \directlua{local hb = require("luaharfbuzz")
%              tex.print(hb.version())}             -> 8.3.0

La consecuencia práctica es esta: hay dos rutas de modelado y, por omisión, luaotfload emplea su propio modelador escrito en Lua. Pida Renderer=Harfbuzz en fontspec y se conmuta a HarfBuzz; el registro muestra entonces entradas como luaotfload.harf.finalize_hlist insertadas en hpack_filter y post_linebreak_filter. Para escrituras de modelado intrincado, como el tibetano o el bengalí, HarfBuzz es el más exacto de los dos, y esa fue justamente la razón de crear LuaHBTeX. En un documento compuesto solo en alfabeto latino no se apreciará diferencia.

Quién construyó LuaTeX y para qué

Escriba luatex --credits y el equipo de desarrollo aparece como Hans Hagen, Hartmut Henkel, Taco Hoekwater y Luigi Scarso, seguido de la lista de proyectos absorbidos: TeX (Knuth), e-TeX (Peter Breitenlohner y otros), Omega (John Plaice, Yannis Haralambous), Aleph (Giuseppe Bilotta), pdfTeX (Hàn Thế Thành), MetaPost, Lua y —anotado «used in LuajitTeX»— LuaJIT (Mike Pall). La veta de Omega y Aleph, los motores multilingües, es la parte que casi nadie conoce. El desarrollo nació en el mundo de ConTeXt y en el proyecto Oriental TeX, dedicado a la composición erudita del árabe y en particular a las ediciones críticas: una subvención propuesta por Idris Samawi Hamid, del Departamento de Filosofía de la Colorado State University, financió la reprogramación del núcleo, con el apoyo de la NTG neerlandesa, la TUG y la DANTE alemana. La idea de hacer programable TeX salió de un deseo muy concreto: componer el árabe con belleza.

Como su base es pdfTeX, LuaTeX escribe PDF directamente y hereda las extensiones e-TeX junto con casi toda la maquinaria de composición de pdfTeX. El espacio de nombres \pdf..., eso sí, se ordenó: buena parte pasa ahora por tres ventanillas —\pdfextension, \pdfvariable, \pdffeedback— que reciben una palabra clave y sus argumentos, mientras que \pdfoutput pasó a ser \outputmode y \pdfximage, \saveimageresource. Rara vez se escribe nada de esto a mano; los paquetes de LaTeX absorben la diferencia. La entrada es UTF-8 desde el principio y las fuentes se nombran desde el sistema con fontspec igual que en XeTeX, pero de la carga se encarga el cargador escrito en Lua por el propio LuaTeX, luaotfload, una adaptación del cargador de fuentes de ConTeXt para Plain TeX y LaTeX, hoy mantenida por el grupo latex3 del equipo de LaTeX.

\directlua: ejecutar Lua en mitad del documento

\directlua{...} ejecuta en el acto el Lua que contiene, y lo que tex.print(...) devuelve reingresa en el flujo de entrada de TeX y se compone como texto corriente. Hay un detalle que sorprende a todo el mundo en el primer intento: \directlua{tex.print(2^10)} no imprime 1024 sino 1024.0. En Lua 5.3, la versión que usa LuaTeX, ^ devuelve siempre un número en coma flotante. Para obtener el entero se escribe math.tointeger(2^10). La misma propiedad muerde cuando un resultado de Lua se asigna a una dimensión o a un contador, y el manual de LuaTeX advierte expresamente de que tostring y string.format pueden devolver notación científica y confundir al lado de TeX.

latex
\documentclass{article}
\begin{document}
% careful: 2^10 is a float in Lua 5.3, so this prints 1024.0
Two to the tenth is \directlua{tex.print(2^10)}.

% and this prints 1024
Two to the tenth is \directlua{tex.print(math.tointeger(2^10))}.
\end{document}

Su hermana \latelua{...} se ejecuta, en cambio, cuando se expide (shipout) la página en la que está. Las tareas que no pueden hacerse hasta que los números de página y las coordenadas finales están fijados —dibujar sobre el PDF, adjuntar una anotación— le corresponden a ella. Compile un documento con ambas y el registro muestra la diferencia con claridad: la salida de \directlua aparece antes de [1 y la de \latelua, después. Un apunte práctico: el \directlua en crudo es quisquilloso con las llaves y los caracteres especiales, así que para cualquier cosa de más de una línea conviene el entorno luacode del paquete homónimo, donde el Lua se pega tal cual y sin riesgo.

Callbacks: insertar la propia función en la composición

La verdadera potencia de LuaTeX está en los callbacks. TeX arma el texto como una lista enlazada de nodos —caracteres, cajas y glue, el espacio elástico— y, al registrar un callback, la propia función Lua se invoca en cada una de esas articulaciones y puede inspeccionar y reescribir esa lista de nodos. El LuaTeX de TeX Live 2024 expone 74 callbacks, que el manual agrupa en seis familias: descubrimiento de ficheros, procesamiento de datos, procesamiento de listas de nodos, informe de información, relacionados con PDF y relacionados con fuentes. El ejemplo siguiente funciona de verdad: cuenta en cuántas líneas se ha partido un párrafo e informa del total al terminar la ejecución.

latex
\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
local count = 0
luatexbase.add_to_callback("post_linebreak_filter",
  function(head)
    for line in node.traverse_id(node.id("hlist"), head) do
      count = count + 1
    end
    return head
  end, "count lines")
luatexbase.add_to_callback("stop_run",
  function() texio.write_nl("LINES TYPESET: " .. count) end, "report")
\end{luacode*}
\begin{document}
This paragraph is broken into lines by TeX, and the Lua function
registered on post\_linebreak\_filter counts them as they go past.
\end{document}

El ejemplo registra mediante luatexbase.add_to_callback y no mediante el callback.register en crudo, porque la forma cruda solo admite una función por callback. En el mundo de LaTeX varios paquetes quieren intervenir en el mismo punto, así que luatexbase los reúne en una lista ordenada. Las herramientas para trabajar con nodos son un conjunto de tablas visibles desde Lua: tex es la ventana al estado interno de TeX (registros y dimensiones), node crea, recorre y libera nodos, token maneja los tokens (las unidades mínimas de significado de TeX), font los datos de fuente y status la información de ejecución. Hasta la maquinaria avanzada de fuentes, como luaotfload, está escrita en Lua sobre esta base.

CallbackCuándo se disparaUso típico
process_input_buffercada vez que se lee una línea de entradapreprocesado de la entrada (recibe y devuelve una cadena)
pre_linebreak_filterjusto antes de partir un párrafo en líneasreescribir la lista de nodos antes del corte
post_linebreak_filterinmediatamente después del corte de líneasactuar sobre las líneas ya formadas
hpack_filtercada vez que se arma una caja horizontalajustar a posteriori el contenido de una caja
ligaturing / kerninglas fases de ligaduras e interletrajesustituir el ajuste propio de la fuente
stop_runal final mismo de la ejecucióninformar de totales, limpiar

En qué ha quedado LuaJITTeX

No ha desaparecido. TeX Live 2024 distribuye dos ejecutables, luajittex y luajithbtex, ambos en la versión 1.18.0, y fmtutil.cnf define formatos para ellos. Dentro no está el Lua de referencia sino LuaJIT, una implementación de compilación al vuelo a código máquina. Si se le pregunta directamente, jit.version responde LuaJIT 2.1.0-beta3 mientras que _VERSION responde Lua 5.1. LuaJITTeX queda, pues, anclado al nivel de lenguaje 5.1 y corre sobre una especificación distinta de la del LuaTeX principal, que va por la 5.3. El manual de LuaTeX lo dice sin rodeos: LuaJIT no está sincronizado con el desarrollo del Lua habitual, de modo que LuaJITTeX se queda atrás.

Esta diferencia no es académica. Compile la línea idéntica \directlua{tex.print(2^10)} con los cuatro binarios: luatex y luahbtex imprimen 1024.0, mientras que luajittex y luajithbtex imprimen 1024, porque Lua 5.3 distingue enteros de flotantes y 5.1 no. Cambiar de motor cambia lo que dice el documento, y esa es una de las razones para no recomendar LuaJITTeX como sustituto directo de LuaLaTeX. Hay un segundo hecho, más decisivo: fmtutil.cnf solo define para los motores LuaJIT formatos de estilo Plain. No existe nada equivalente a un luajitlatex. Hacer correr LaTeX sobre LuaJIT exigiría construirse un formato propio, así que la formulación exacta es que, en la práctica, no figura en el menú de un usuario de LaTeX.

Una segunda línea sucesoria es LuaMetaTeX, una reescritura más ligera de LuaTeX que sustenta el ConTeXt moderno (LMTX / MkXL). Trae la composición matemática rehecha, gran cantidad de primitivas nuevas y la línea Lua 5.4. TeX Live 2024 incluye luametatex 2.11.02, y la propia orden context es un enlace simbólico a él. El reparto es claro: LuaTeX para trabajar en LaTeX, LuaMetaTeX para el ConTeXt actual.

¿Es LuaLaTeX realmente lento? Una medición

Es realmente más lento, pero se suele situar mal dónde está el coste. El arranque es casi idéntico: en un documento de una página prácticamente vacía, pdfLaTeX tardó 0,20 s, XeLaTeX 0,30 s y LuaLaTeX 0,32 s (misma máquina, el mejor de varias pasadas). La brecha se abre en la parte que crece con la cantidad de composición. Sobre el mismo documento matemático de 417 páginas, pdfLaTeX tardó 0,40 s, XeLaTeX 0,53 s y LuaLaTeX 1,41 s. Reste el arranque y mire solo la composición: XeLaTeX cuesta unas 1,2 veces pdfLaTeX y LuaLaTeX más de cinco veces. Dicho al revés: en un artículo de diez páginas la diferencia es imperceptible.

MediciónpdfLaTeXXeLaTeXLuaLaTeX
1-page document0,20 s0,30 s0,32 s
417-page document0,40 s0,53 s1,41 s
typesetting onlyreferenciaunas 1,2 vecesunas 5,6 veces

Tres respuestas prácticas. Primera, mientras se redacta, reduzca el número de idas y vueltas en lugar de perseguir la velocidad bruta: una compilación automatizada como latexmk, que solo rehace lo que ha cambiado, aporta más que la elección de motor. Segunda, no lo confunda con el coste único de la caché de fuentes. Cuando luaotfload encuentra una fuente que no conoce, la explora e indexa, y esa pasada concreta es muchísimo más lenta. La mayoría de los relatos sobre «LuaLaTeX tardando decenas de segundos» son eso; desde la segunda pasada se vuelve a las cifras de arriba. Tercera, en integración continua, cuente pasadas y recompilaciones, no el coste de una sola. Si la construcción encadena dos o tres pasadas por las referencias cruzadas y el índice, cualquier diferencia por pasada queda multiplicada por tres.

MetaPost incorporado y el japonés con LuaTeX-ja

LuaTeX lleva incorporado el motor de dibujo MetaPost como biblioteca, MPlib, de modo que las figuras se generan en el mismo proceso sin llamar a ningún programa externo. Desde LaTeX se accede a él mediante el paquete luamplib y se escribe MetaPost directamente dentro de un entorno mplibcode. Compile uno y examine el registro: no hay rastro alguno del lanzamiento de un mpost externo. Parece un detalle menor y sin embargo cuenta: las figuras siguen construyéndose en entornos con el escape de shell desactivado y en integración continua.

De la composición japonesa se ocupa LuaTeX-ja (paquete luatexja). Reimplementa del lado de Lua, mediante callbacks, el saber tipográfico japonés de pTeX —escritura vertical, el espaciado entre caracteres y el tratamiento de la puntuación gobernados por las JFM (Japanese Font Metrics), y el espacio entre texto japonés y occidental—, lo que lo convierte en el mayor ejemplo práctico de la maquinaria de callbacks descrita arriba. Con luatexja-fontspec también pueden seleccionarse fuentes japonesas al estilo de fontspec. Una precaución: el modelado HarfBuzz de LuaHBTeX y los mecanismos de escritura vertical y CID de LuaTeX-ja deben combinarse con cuidado. El manual de LuaTeX-ja advierte de que las fuentes japonesas definidas a través de HarfBuzz pueden dar resultados no deseados. Para un manuscrito real, obtenga primero un PDF con las fuentes Harano Aji incluidas en TeX Live y los ajustes estándar, e introduzca las características OpenType o las opciones de HarfBuzz solo tras probarlas en algo pequeño.

latex
\documentclass{ltjsarticle}
\usepackage{luatexja-fontspec}
% Harano Aji ships with TeX Live, so this builds anywhere
\setmainjfont{HaranoAjiMincho-Regular}
\setsansjfont{HaranoAjiGothic-Medium}
\begin{document}
\section{日本語}
Text and mathematics $E=mc^2$ go through the same engine.
\end{document}

Empezar un documento nuevo en LuaLaTeX

  • Empezar desde un fuente en Unicode. Nada de inputenc ni de fontenc. Migrar conservando las declaraciones de la era pdfLaTeX solo trae avisos y una configuración de fuentes duplicada.
  • Hacer de fontspec la única fuente de selección tipográfica. En un manuscrito compartido, conseguir primero una compilación que funcione con las fuentes incluidas en TeX Live y sustituir después.
  • Usar luatexja para el japonés. Cubre la escritura vertical, las JFM y el espacio entre texto japonés y occidental.
  • Poner el Lua algo extenso en un entorno luacode. El \directlua en crudo es quisquilloso con las llaves y los caracteres especiales.
  • Registrar los callbacks mediante luatexbase.add_to_callback. El callback.register en crudo solo admite una función por punto de enganche.
  • Construir antes una muestra pequeña. Una página que ponga a prueba la fuente de texto, la japonesa, las fórmulas y la bibliografía, y usarla luego como plantilla.

La regla en una frase: LuaLaTeX si se quiere entrar en la composición misma; XeLaTeX si solo se buscan las fuentes del sistema por su nombre; pdfLaTeX si el trabajo es mayoritariamente en inglés y pesan la rapidez y la compatibilidad. Existe aparte una página que compara los tres de frente. Como el equipo de LaTeX orienta el desarrollo nuevo hacia LuaTeX, LuaLaTeX se va acercando a ser la opción por omisión en los flujos de trabajo Unicode/OpenType que empiezan hoy, sobre todo en aquellos en los que se quiere programar algo.