XeLaTeX、LuaLaTeX、fontspec、Unicode、正規化

XeLaTeX、LuaLaTeX、fontspec、Unicode、正規化

LuaTeX と XeLaTeX が Unicode 合成文字を正規化する方法に困っています。つまり、NFC / NFD です。

次の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}

LuaLaTeX を使用すると次のようになります: LuaLaTeXでコンパイルされた例

ご覧のとおり、LuaはUnicode文字を正規化しません。また、Linux Libertineにはバグがあります(http://sourceforge.net/p/linuxlibertine/bugs/266/)、私は性格が悪いです。

XeLaTeXを使用すると、XeLaTeXでコンパイルされた例

ご覧のとおり、Unicode は正規化されています。

私の質問は3つあります:

  1. なぜXeLaTeXは(NFCで)標準化されたのか、私は使っていないのに\XeTeXinputnormalization
  2. この機能は以前から変更されましたか?私の以前のTeXLive 2012では悪い結果になりました(この時点で書いた記事を参照してください)http://geekographie.maieul.net/Normalisation-des-caracteres
  3. \XeTeXinputnormalizationLuaTeXには XeTeXのようなオプションがありますか?

答え1

私は XeTeX を使用していないため、最初の 2 つの質問の答えはわかりませんが、3 番目の質問に対するオプションを提供したいと思います。

感謝アーサーのコードLuaLaTeXでUnicode正規化の基本パッケージを作成することができました。現在のLuaTeXで動作させるには、コードを少し変更するだけで済みました。ここではメインのLuaファイルのみを掲載しますが、完全なプロジェクトは以下で入手できます。Github を非正規化として

使用例:

\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}

(このファイルの正しいバージョンは Github にありますが、この例では結合された文字が誤って転送されています)

パッケージの主なアイデアは次のとおりです。入力を処理し、文字の後に複合マークが続く場合は、それを正規化された NFC 形式で置き換えます。2 つの方法が提供されており、最初のアプローチは、ノード処理コールバックを使用して、分解されたグリフを正規化された文字に置き換えることでした。これには、ノード属性を使用して、どこでも処理のオン/オフを切り替えることができるという利点があります。もう 1 つの可能な機能は、現在のフォントに正規化された文字が含まれているかどうかを確認し、含まれていない場合は元の形式を使用することです。残念ながら、私のテストでは、一部の文字で失敗しました。特に、composed はではなくíとしてノードに含まれており、正規化後に正しい文字が生成されないため、代わりに合成文字が使用されます。しかし、これにより、アクセントの位置が不適切な出力が生成されます。したがって、この方法は何らかの修正が必要であるか、完全に間違っています。dotless i + ´i + ´

したがって、もう 1 つの方法は、コールバックを使用して、process_input_bufferディスクから読み取られる入力ファイルを正規化することです。この方法では、フォントの情報を使用することも、行の途中でオフにすることもできませんが、実装が非常に簡単です。コールバック関数は次のようになります。

function buffer_callback(line) 
  return NFC(line)
end 

これは、ノード処理バージョンに 3 日間費やした後の本当に素晴らしい発見です。

興味深いことに、これは 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

さて、写真を撮る時間です。

正規化なし:

ここに画像の説明を入力してください

ギリシャ文字の組み合わせが間違っていることがわかります。他の組み合わせはLinux Libertineでサポートされています。

ノード正規化の場合:

ここに画像の説明を入力してください

ギリシャ文字は正しいですが、íin firstpřílišが間違っています。これが私が話していた問題です。

次にバッファの正規化を行います。

ここに画像の説明を入力してください

すべては大丈夫です

関連情報