XeLaTeX, LuaLaTeX, fontspec, unicode y normalización

XeLaTeX, LuaLaTeX, fontspec, unicode y normalización

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: Ejemplo compilado con LuaLaTeX

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 obtengoEjemplo compilado con XeLaTeX

Como puede ver, Unicode está normalizado.

Mis tres preguntas son:

  1. Por qué XeLaTeX se ha normalizado (en NFC), a pesar de que no lo he usado\XeTeXinputnormalization
  2. ¿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)
  3. ¿LuaTeX tiene opciones como las que hay \XeTeXinputnormalizationen 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_bufferla 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:

ingrese la descripción de la imagen aquí

Puedes ver que el carácter griego compuesto es incorrecto; Linux Libertine admite otras combinaciones.

con normalización de nodos:

ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

todo está bien ahora

información relacionada