Normalización Unicode en Emacs / Org Mode / LuaLaTeX
Una buena parte de los ficheros de word originales de donde extraigo el material (vía pandoc
) para componer el
Diccionario Hispánico de la Tradición Clásica venía con una curiosa anomalía técnica. Resulta que, por alguna extraña
razón que se me escapa, muchos caracteres con diacrítico aparecen en la forma combinada (NFD) y no en la esperable forma
precompuesta (NFC). Los casos son muchos, variados y aleatorios, de tal forma que a veces aparece el carácter de la «a»
con acento (por ejemplo) en una forma y otras veces en la otra (?). Así que corregirlo a mano era un poco latoso. La
fuente con que estoy componiendo tampoco tiene ninguna propiedad otf
que haga la normalización de oficio, de modo que,
¿cómo normalizar ese desaguisado?
Antes de continuar, no está de más refrescar un poco la memoria acerca de cómo Unicode gestiona los caracteres
complejos, donde una letra base debe ir acompañada por uno o más signos diacríticos. El estándar, por supuesto (y sin
ahondar demasiado en la cuestión), representa esos grupos indisolubles de signos de una forma canónica. Y cuenta con
dos vías para hacerlo. La normal, es decir, la que vemos en documentos electrónicos, navegadores o simplemente cuando
escribimos español en nuestro teclado y pulsamos letras como la «a» con acento, es la denominada «Forma Normalizada de
Composición» (NFC, en sus siglas en inglés). Aquí las letras con sus diacríticos forman un único carácter
«precompuesto». Si en mi teclado, pues, escribo la letra «ñ», lo que estoy añadiendo es el carácter que Unicode llama
LATIN SMALL LETTER N WITH TILDE
y que recibe el código U+00f1
. Naturalmente, los caracteres precompuestos son los
deseables, porque nos ofrecen un diseño siempre consistente. Pero Unicode también se asegura una segunda vía «de
escape», que puede ser útil en ciertas emergencias de carencias de caracteres necesarios o para algunos análisis
internos de los programas. Se trataría de la «Forma Normalizada de Descomposición» (NFD), donde el carácter complejo se
forma mediante la adición de la letra base y unos signos combinatorios que en las fuentes son siempre de «espacio cero»
(zero width). Es decir, se inserta el signo y éste no añade un espacio, para que quede ubicado con más o menos fortuna
sobre o bajo la letra a la que el diacrítico debe afectar1. Entre las dos formas de representar los caracteres
complejos Unicode establece una equivalencia canónica. De suerte que el signo precompuesto de la «ñ» que describimos más
arriba debe descomponerse, según inapelable mandato de Unicode, en dos caracteres (cuando estamos usando NFD, se
entiende): el carácter U+006e
(LATIN SMALL LETTER N
) y el U+0303
(COMBINING TILDE
).
Bien, ya hemos refrescado la memoria, pero estábamos en lo de arreglar el estado catastrófico de nuestros documentos. Podemos echar mano de varios remedios, que paso sin demora a comentar.
El incombustible Python
Normalizar el Unicode en Python es una tarea fácil gracias al módulo unicodedata
. Si tenemos la palabra:
cuñado
donde los caracteres son:
c ... u ... n ... ̃ ... a ... d ... o
bastaría algo así como:
import unicodedata a="cuñado" print(unicodedata.normalize("NFC",a))
cuñado
donde los caracteres serían:
c ... u ... ñ ... a ... d ... o
Con esta premisa, no sería muy traumático crearse algún script. Lo que pasa es que a mí Python me resulta espeso y aburrido. Y me muevo más como pez en el agua dentro de Elisp.
Elisp, Emacs y Org Mode
En Emacs Lisp la normalización es muchísimo más sencilla si cabe, mediante la función ucs-normalize-NFC-string
, del
paquete ucs-normalize.el
2. Con la palabra del ejemplo anterior, podemos evaluar la expresión:
(ucs-normalize-NFC-string "cuñado")
cuñado
(es decir, con la «ñ» otra vez).
Como el texto lo compilo con LuaLATEX desde Org Mode, me bastó con escribir e incluir esta función en el documento Org, que se evalúa durante la exportación:
#+begin_src emacs-lisp :exports results :results none (defun mi-filtro-latex-normaliza-NFC (texto backend info) (when (org-export-derived-backend-p backend 'latex) (setq texto (ucs-normalize-NFC-string texto)))) #+end_src
Y mano de santo.
El paquete uninormalize
Luego descubrí una tercera solución, dentro de LuaLATEX, que es la del paquete uninormalize
. No está en CTAN de
momento, pero se puede descargar de su repo de github aquí: https://github.com/michal-h21/uninormalize No lo he probado
mucho, pero parece que funciona bien.
Y por último…
Javier Bezos nos comentaba en un pasado mensaje a la lista de correo de ES-TeX que la próxima versión de LuaTEX
incorporará el motor de renderizado de fuentes HarfBuzz. Lo podremos disfrutar en la entrega de 2020 de TeXLive, pero
para los impacientes ya está disponible en la versión de desarrollo de LuaTEX. Sólo habrá que cargar con
fontspec
la opción para las fuentes Renderer=Harfbuzz
. Y ya no serían necesarias las artimañas anteriores.
Estrambote
Para ver si una palabra lleva caracteres NFC o NFD, por cierto, escribí esta funcioncilla en Elisp, que me hace el apaño y me es muy útil. Descompone los caracteres de la palabra donde está el cursor y los muestra en una ventana temporal, cada uno junto a su código:
(defun analiza-caracteres-palabra () (interactive) (if (not (current-word t t)) (error "Ninguna palabra que analizar...") (let ((palabra (current-word t t))) (save-excursion (with-temp-buffer (insert palabra) (goto-char (point-min)) (while (re-search-forward "\\(.\\)" nil t) (replace-match (concat (match-string 1) "\s" "(" (format "#%x" (string-to-char (match-string 1))) ")\s...\s") t nil)) (setq palabra (buffer-string))) (when (get-buffer "*palabra analizada*") (kill-buffer "*palabra analizada*")) (get-buffer-create "*palabra analizada*") (set-buffer "*palabra analizada*") (insert palabra) (temp-buffer-window-show "*palabra analizada*" '((display-buffer-below-selected display-buffer-at-bottom) (inhibit-same-window . t) (window-height . fit-window-to-buffer)))))))
∞
Publicado: 29/02/20
Última actualización: 21/01/22
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.
Notas al pie de página:
Lo normal es que el diacrítico no se ubique del todo bien, ya que las letras tienen un ancho variable. Para
ajustarlo mejor las fuentes OpenType pueden aplicar ciertas propiedades de posicionamiento para diacríticos combinados,
como mark
.
Tenemos también su contrapartida inversa ucs-normalize-NFD-string
. Y también para una región:
ucs-normalize-NFC-region
y ucs-normalize-NFD-region