La lunotipia. Tipografía digital, TeX y cafeína

La sustitución contextual OpenType en LuaTeX
(y un ejemplo con la escritura griega)

Una de las características más atractivas que ofrece la tecnología OpenType es aquella que consiste en sustituir unas letras por otras dependiendo de un determinado contexto. Puede usarse para resolver ciertas necesidades de tipografía pragmática, pero también habrá escenarios para satisfacer otras de índole más bien estilística o histórica, casi siempre sepultadas en el olvido tras la llegada de los ordenadores al mundo de la producción editorial. A este segundo grupo pertenecería la sustitución en mitad de palabra de la letra griega beta minúscula β por su variante estilística curvada (o beta sin descendente) ϐ, procedimiento muy habitual en la tradición impresora francesa del pasado siglo y que, aplicado en los textos y en las condiciones adecuadas, puede crear un efecto muy grato al lector (o al menos al lector que soy yo).

Tenemos (si somos consecuentes con la tesis Unicode que distingue caracteres de glifos) un único carácter para la letra beta, idea platónica que puede concretarse textualmente en dos posibles glifos: el de la beta «normal» con descendente y el de la beta curvada, que el estándar Unicode sitúa bajo el código U+03D0 y que denomina «Greek beta symbol» (fig. 1)

Lo que viene a aportar OpenType es el añadir a la fuente tipográfica una propiedad —digamos— tridimensional, que permite que el carácter opte por un glifo u otro según una serie de instrucciones que la fuente pueda incluir. Basta con activar la etiqueta adecuada (que casi siempre suele ser calt, de «contextual alternates»), por ejemplo en nuestro LuaLATEX con el paquete Fontspec. A los efectos de transmisión textual —y esto es lo esencial, insistimos— ambos glifos siguen siendo el mismo carácter beta, como podemos comprobar si copiamos cualquier pasaje de un PDF donde se haya operado la sustitución contextual y lo pegamos en un editor de texto.

beta_symbol.png

Figura 1: Descripción del carácter Unicode GREEK BETA SYMBOL

Ahora bien, OpenType se limita a ofrecer los protocolos necesarios. Otro cantar es hasta qué punto los implementan los diseñadores de fuentes. Por supuesto, si nuestra fuente no dispone de esa característica, como sucede en la gran mayoría de fuentes que incluyen soporte para la escritura griega, siempre podremos añadirla nosotros mismos mediante el editor de fuentes Fontforge. Pero hoy toca hablar aquí de otra manera mucho más simple, si cabe, de hacerlo, que es dentro de LuaTEX y echando mano de la utilísima función Lua fonts.handlers.otf.addfeature, que nos permite definir en el pre-procesado del material textual ciertas características OpenType «al vuelo», gracias a la habilidad que LuaTEX tiene de «injertar», como su propio nombre anuncia, scripts y código Lua en el proceso de compilación. LuaTEX incluye en sus engranajes un intérprete de Lua, lo que supone que podemos puentear las primitivas de TEX mediante este versátil y ligero lenguaje. En lo que atañe a nuestra deseada sustitución, la ventaja de hacerlo así es evidente, pues añadiremos la propiedad OpenType a cualquier fuente que se nos antoje, sin necesidad de editarla y de crear versiones modificadas de ésta. Siempre y cuándo, claro, la fuente disponga de los glifos necesarios con que jugar, pues fonts.handlers.otf.addfeature no crea letras de la nada, sino que opera sobre la posición y el contexto de los glifos de que ya dispone la fuente.

Veamos una prueba, por ejemplo, con la fuente Linux Libertine, que sí dispone de esos glifos. Añadimos lo siguiente a nuestro archivo fuente de LATEX, convenientemente encerrado en la primitiva \directlua, necesaria para poder insertar código Lua.

Empezamos por definir una variable y asignarle una tabla de todos los caracteres griegos que deben ir antes del glifo a sustituir:

letras_griegas = { "α", "ἀ", "ἁ", "ἂ", "ἃ", "ἄ", "ἅ", "ἆ", "ἇ",
"Ἀ", "Ἁ", "Ἂ", "Ἃ", "Ἄ", "Ἅ", "Ἆ", "Ἇ", "ε", "ἐ", "ἑ", "ἒ", "ἓ", "ἔ", "ἕ",
"Ἐ", "Ἑ", "Ἒ", "Ἓ", "Ἔ", "Ἕ", "η", "ἠ", "ἡ", "ἢ", "ἣ", "ἤ", "ἥ", "ἦ", "ἧ",
"Ἠ", "Ἡ", "Ἢ", "Ἣ", "Ἤ", "Ἥ", "Ἦ", "Ἧ", "ι", "ἰ", "ἱ", "ἲ", "ἳ", "ἴ", "ἵ", "ἶ", "ἷ",
"Ἰ", "Ἱ", "Ἲ", "Ἳ", "Ἴ", "Ἵ", "Ἶ", "Ἷ", "ο", "ὀ", "ὁ", "ὂ", "ὃ", "ὄ", "ὅ",
"Ὀ", "Ὁ", "Ὂ", "Ὃ", "Ὄ", "Ὅ", "υ", "ὐ", "ὑ", "ὒ", "ὓ", "ὔ", "ὕ", "ὖ", "ὗ",
"Ὑ", "Ὓ", "Ὕ", "Ὗ", "ω", "ὠ", "ὡ", "ὢ", "ὣ", "ὤ", "ὥ", "ὦ", "ὧ",
"Ὠ", "Ὡ", "Ὢ", "Ὣ", "Ὤ", "Ὥ", "Ὦ", "Ὧ",
"ὰ","ά","ὲ","έ","ὴ","ή","ὶ","ί","ὸ","ό","
ὺ","ύ","ὼ","ώ","ᾀ","ᾁ","ᾂ","ᾃ","ᾄ","ᾅ","ᾆ","ᾇ",
"ἈΙ","ᾉ", "ἊΙ","ᾋ", "ἌΙ","ᾍ", "ἎΙ","ᾏ",
"ᾐ","ᾑ","ᾒ","ᾓ", "ᾔ","ᾕ","ᾖ","ᾗ","ἨΙ","ᾙ",
"ἪΙ","ᾛ", "ἬΙ","ᾝ", "ἮΙ","ᾟ", "ᾠ","ᾡ","ᾢ","ᾣ","ᾤ","ᾥ","ᾦ","ᾧ",
"ὨΙ","ᾩ", "ὪΙ", "ᾫ", "ὬΙ","ᾭ", "ὮΙ","ᾯ",
"ᾰ","ᾱ","ᾲ","ᾳ","ᾴ","ᾶ","ᾷ","Ᾰ","Ᾱ","ῆ","ῇ",
"ῖ","ῗ","Ῐ","Ῑ","ῠ","ῡ","ῢ","ΰ","ῤ","ῥ","ῦ","ῧ","Ῠ","Ῡ","Ῥ",
"ῲ","ῳ","ῴ","ῶ","ῷ", "Ά","Έ","Ή","Ί","Ό","Ύ","Ώ","ΐ",
"Α","Β","Γ","Δ","Ε","Ζ","Η","Θ","Ι","Κ",
"Λ","Μ","Ν","Ξ","Ο","Π","Ρ","Σ","Τ","Υ","Φ","Χ","Ψ","Ω","Ϊ","Ϋ",
"ά", "έ","ή","ί","ΰ","α","β","γ","δ","ε","ζ","η","θ",
"ι","κ","λ","μ","ν","ξ","ο","π","ρ","ς","σ","τ","υ",
"φ","χ","ψ","ω","ϓ","ϔ","ϕ","ϖ","ϗ", "Ϙ", "ϙ","Ϛ","ϛ","Ϝ","ϝ", "ϐ" }

Y a continuación, la nueva propiedad OpenType:

fonts.handlers.otf.addfeature{
   name = "contextualtest",
   type = "chainsustitution",
   lookups = {
      {
         type = "sustitution",
         data = {
            ["β"] = "ϐ",
         },

      },
   },
   data = {
      rules = {
         {
            before  = { letras_griegas  },
            current = { { "β" } },
            lookups = { 1 },
         },
      },
   },
}

Explicándolo un poco a trazo grueso. Hemos creado una propiedad OpenType que llamamos contextualtest, y declaramos que es de tipo chainsustitution, es decir, queremos definir una cadena de sustitución contextual. Le incluimos un lookup que indica en qué consiste la sustitución («β» a «ϐ»), y más abajo añadimos las reglas. Las cuales consisten en que todos los caracteres encerrados bajo la variable before propiciarán la sustitución de beta simple a curvada sólo si ellos se sitúan inmediatamente antes de la letra.

Bien, ya casi lo tenemos. Esto nos soluciona el 99 % de los contextos para sustituir la beta en mitad de palabra. Pero la queremos, precisamente, así, en mitad de palabra y no al final, lo que no evitaría el código anterior. Naturalmente, son muy raros los casos en que aparezca una beta en esa posición, si pensamos en la escritura griega real. Pero algún que otro contexto puede surgir, así que sumamos al anterior este otro código, que evitará que nuestra beta se sustituya por la variante curvada al final de una palabra:

fonts.handlers.otf.addfeature{
   name = "contextualtest2",
   type = "chainsustitution",
   lookups = {
      {
         type = "sustitution",
         data = {
            ["ϐ"] = "β",
         },
      },
   },
   data = {
      rules = {
         {
            after  = { { " ", 0xFFFC, ",", ".", "´", "·"} },
            current = { { "ϐ" } },
            lookups = { 1 },
         },
      },
   },
}

Y pasamos a probar nuestro código, cargando las dos nuevas propiedades OpenType con fontspec. Definimos entonces, para la Linux Libertine, dos familias, con y sin sustitución (resultado de la compilación en la fig. 2):

\documentclass{article}
\usepackage{fontspec}
% Aquí iría todo el código Lua...
\setmainfont{Linux Libertine}
\usepackage[greek.ancient]{babel}
\newfontfamily\nobetasub{Linux Libertine}
\newfontfamily\sibetasub[RawFeature={+contextualtest,+contextualtest2}]{Linux Libertine}
\begin{document}
\nobetasub
βαρβάρων - ἐβούλετο - ιβ´
\sibetasub
βαρβάρων - ἐβούλετο - ιβ´
\end{document}

contextual2.png

Figura 2: Resultado de la compilación

Publicado: 31/08/19

Última actualización: 21/01/22


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs