
Me preocupa la forma en que LuaTeX y XeLaTeX normalizan los caracteres compuestos Unicode. Me refiero a NFC/NFD.
Ver el siguiente MWE
\documentclass{article}
\usepackage{fontspec}
\setmainfont{Linux Libertine O}
\begin{document}
ᾳ GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING GREEK YPOGEGRAMMENI (U+0345)
ᾳ GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI (U+1FB3)
\end{document}
Con LuaLaTeX obtengo:
Como puede ver, Lua no normaliza el carácter Unicode y Linux Libertine tiene un error (http://sourceforge.net/p/linuxlibertine/bugs/266/), tengo mal carácter.
Con XeLaTeX obtengo
Como puede ver, Unicode está normalizado.
Mis tres preguntas son:
- Por qué XeLaTeX se ha normalizado (en NFC), a pesar de que no lo he usado
\XeTeXinputnormalization
- ¿Esta característica cambió con respecto al pasado? Porque mi envío anterior con TeXLive 2012 fue un mal resultado (ver los artículos que escribí en este momentohttp://geekographie.maieul.net/Normalisation-des-caracteres)
- ¿LuaTeX tiene opciones como las que hay
\XeTeXinputnormalization
en XeTeX?
Respuesta1
No sé la respuesta a las dos primeras preguntas, ya que no uso XeTeX, pero quiero ofrecer una opción para la tercera pregunta.
Gracias aEl código de Arturo.Pude crear un paquete básico para la normalización Unicode en LuaLaTeX. El código sólo necesitaba ligeras modificaciones para funcionar con el LuaTeX actual. Publicaré aquí sólo el archivo principal de Lua, el proyecto completo está disponible enGithub como uninormalizado.
Uso de muestra:
\documentclass{article}
\usepackage{fontspec}
\usepackage[czech]{babel}
\setmainfont{Linux Libertine O}
\usepackage[nodes,buffer=false, debug]{uninormalize}
\begin{document}
Some tests:
\begin{itemize}
\item combined letter ᾳ %GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING GREEK YPOGEGRAMMENI (U+0345)
\item normal letter ᾳ% GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI (U+1FB3)
\end{itemize}
Some more combined and normal letters:
óóōōöö
Linux Libertine does support some combined chars: \parbox{4em}{příliš}
\end{document}
(tenga en cuenta que la versión correcta de este archivo está en Github; las letras combinadas se transfirieron incorrectamente en este ejemplo)
La idea principal del paquete es la siguiente: procesar la entrada y, cuando se encuentra una letra seguida de marcas combinadas, se reemplaza por el formato NFC normalizado. Se proporcionan dos métodos; mi primer enfoque fue utilizar devoluciones de llamada de procesamiento de nodos para reemplazar glifos descompuestos con caracteres normalizados. Esto tendría la ventaja de que sería posible activar y desactivar el procesamiento en cualquier lugar, utilizando atributos de nodo. La otra característica posible podría ser verificar si la fuente actual contiene caracteres normalizados y usar el formato original si no es así. Desafortunadamente, en mis pruebas falla con algunos caracteres, en particular los compuestos í
están en los nodos como dotless i + ´
, en lugar de i + ´
, que después de la normalización no produce el carácter correcto, por lo que se usan caracteres compuestos en su lugar. Pero esto produce resultados con una mala colocación del acento. Así que este método necesita alguna corrección o es totalmente incorrecto.
Entonces, el otro método es utilizar process_input_buffer
la devolución de llamada para normalizar el archivo de entrada a medida que se lee desde el disco. Este método no permite usar información de las fuentes, ni permite desactivarlas en medio de la línea, pero es mucho más fácil de implementar, la función de devolución de llamada puede verse así:
function buffer_callback(line)
return NFC(line)
end
lo cual es un hallazgo realmente agradable después de tres días dedicados a la versión de procesamiento de nodos.
Por curiosidad este es el paquete Lua:
local M = {}
dofile("unicode-names.lua")
dofile('unicode-normalization.lua')
local NFC = unicode.conformance.toNFC
local char = unicode.utf8.char
local gmatch = unicode.utf8.gmatch
local name = unicode.conformance.name
local byte = unicode.utf8.byte
local unidata = characters.data
local length = unicode.utf8.len
M.debug = false
-- for some reason variable number of arguments doesn't work
local function debug_msg(a,b,c,d,e,f,g,h,i)
if M.debug then
local t = {a,b,c,d,e,f,g,h,i}
print("[uninormalize]", unpack(t))
end
end
local function make_hash (t)
local y = {}
for _,v in ipairs(t) do
y[v] = true
end
return y
end
local letter_categories = make_hash {"lu","ll","lt","lo","lm"}
local mark_categories = make_hash {"mn","mc","me"}
local function printchars(s)
local t = {}
for x in gmatch(s,".") do
t[#t+1] = name(byte(x))
end
debug_msg("characters",table.concat(t,":"))
end
local categories = {}
local function get_category(charcode)
local charcode = charcode or ""
if categories[charcode] then
return categories[charcode]
else
local unidatacode = unidata[charcode] or {}
local category = unidatacode.category
categories[charcode] = category
return category
end
end
-- get glyph char and category
local function glyph_info(n)
local char = n.char
return char, get_category(char)
end
local function get_mark(n)
if n.id == 37 then
local character, cat = glyph_info(n)
if mark_categories[cat] then
return char(character)
end
end
return false
end
local function make_glyphs(head, nextn,s, lang, font, subtype)
local g = function(a)
local new_n = node.new(37, subtype)
new_n.lang = lang
new_n.font = font
new_n.char = byte(a)
return new_n
end
if length(s) == 1 then
return node.insert_before(head, nextn,g(s))
else
local t = {}
local first = true
for x in gmatch(s,".") do
debug_msg("multi letter",x)
head, newn = node.insert_before(head, nextn, g(x))
end
return head
end
end
local function normalize_marks(head, n)
local lang, font, subtype = n.lang, n.font, n.subtype
local text = {}
text[#text+1] = char(n.char)
local head, nextn = node.remove(head, n)
--local nextn = n.next
local info = get_mark(nextn)
while(info) do
text[#text+1] = info
head, nextn = node.remove(head,nextn)
info = get_mark(nextn)
end
local s = NFC(table.concat(text))
debug_msg("We've got mark: " .. s)
local new_n = node.new(37, subtype)
new_n.lang = lang
new_n.font = font
new_n.char = byte(s)
--head, new_n = node.insert_before(head, nextn, new_n)
-- head, new_n = node.insert_before(head, nextn, make_glyphs(s, lang, font, subtype))
head, new_n = make_glyphs(head, nextn, s, lang, font, subtype)
local t = {}
for x in node.traverse_id(37,head) do
t[#t+1] = char(x.char)
end
debug_msg("Variables ", table.concat(t,":"), table.concat(text,";"), char(byte(s)),length(s))
return head, nextn
end
local function normalize_glyphs(head, n)
--local charcode = n.char
--local category = get_category(charcode)
local charcode, category = glyph_info(n)
if letter_categories[category] then
local nextn = n.next
if nextn.id == 37 then
--local nextchar = nextn.char
--local nextcat = get_category(nextchar)
local nextchar, nextcat = glyph_info(nextn)
if mark_categories[nextcat] then
return normalize_marks(head,n)
end
end
end
return head, n.next
end
function M.nodes(head)
local t = {}
local text = false
local n = head
-- for n in node.traverse(head) do
while n do
if n.id == 37 then
local charcode = n.char
debug_msg("unicode name",name(charcode))
debug_msg("character category",get_category(charcode))
t[#t+1]= char(charcode)
text = true
head, n = normalize_glyphs(head, n)
else
if text then
local s = table.concat(t)
debug_msg("text chunk",s)
--printchars(NFC(s))
debug_msg("----------")
end
text = false
t = {}
n = n.next
end
end
return head
end
--[[
-- These functions aren't needed when processing buffer. We can call NFC on the whole input line
local unibytes = {}
local function get_charcategory(s)
local s = s or ""
local b = unibytes[s] or byte(s) or 0
unibytes[s] = b
return get_category(b)
end
local function normalize_charmarks(t,i)
local c = {t[i]}
local i = i + 1
local s = get_charcategory(t[i])
while mark_categories[s] do
c[#c+1] = t[i]
i = i + 1
s = get_charcategory(t[i])
end
return NFC(table.concat(c)), i
end
local function normalize_char(t,i)
local ch = t[i]
local c = get_charcategory(ch)
if letter_categories[c] then
local nextc = get_charcategory(t[i+1])
if mark_categories[nextc] then
return normalize_charmarks(t,i)
end
end
return ch, i+1
end
-- ]]
function M.buffer(line)
--[[
local t = {}
local new_t = {}
-- we need to make table witl all uni chars on the line
for x in gmatch(line,".") do
t[#t+1] = x
end
local i = 1
-- normalize next char
local c, i = normalize_char(t, i)
new_t[#new_t+1] = c
while t[i] do
c, i = normalize_char(t,i)
-- local c = t[i]
-- i = i + 1
new_t[#new_t+1] = c
end
return table.concat(new_t)
--]]
return NFC(line)
end
return M
y ahora es el momento de algunas fotos.
sin normalización:
Puedes ver que el carácter griego compuesto es incorrecto; Linux Libertine admite otras combinaciones.
con normalización de nodos:
Las letras griegas son correctas, pero í
la primera příliš
es incorrecta. este es el tema del que estaba hablando.
y ahora la normalización del buffer:
todo está bien ahora