
Ich bin beunruhigt über die Art und Weise, wie LuaTeX und XeLaTeX zusammengesetzte Unicode-Zeichen normalisieren. Ich meine NFC/NFD.
Siehe folgende 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}
Mit LuaLaTeX erhalte ich:
Wie Sie sehen, normalisiert Lua keine Unicode-Zeichen, und da Linux Libertine einen Fehler hat (http://sourceforge.net/p/linuxlibertine/bugs/266/), ich habe einen schlechten Charakter.
Mit XeLaTeX erhalte ich
Wie Sie sehen, ist Unicode normalisiert.
Meine drei Fragen sind:
- Warum XeLaTeX normalisiert wurde (in NFC), obwohl ich es nicht verwendet habe
\XeTeXinputnormalization
- Hat sich diese Funktion gegenüber der Vergangenheit geändert? Weil mein vorheriger, mit TeXLive 2012 gesendeter Beitrag ein schlechtes Ergebnis war (siehe die Artikel, die ich damals geschrieben habehttp://geekographie.maieul.net/Normalisation-des-caracteres)
- Verfügt LuaTeX über Optionen wie
\XeTeXinputnormalization
XeTeX?
Antwort1
Ich kenne die Antwort auf die ersten beiden Fragen nicht, da ich XeTeX nicht verwende, aber ich möchte eine Option für die dritte Frage anbieten.
Dank anArthurs CodeIch konnte ein Basispaket für die Unicode-Normalisierung in LuaLaTeX erstellen. Der Code musste nur geringfügig geändert werden, um mit dem aktuellen LuaTeX zu funktionieren. Ich werde hier nur die Haupt-Lua-Datei veröffentlichen, das vollständige Projekt ist verfügbar unterGithub als uninormalize.
Beispielverwendung:
\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}
(Beachten Sie, dass die richtige Version dieser Datei auf Github ist, kombinierte Buchstaben wurden in diesem Beispiel falsch übertragen)
Die Hauptidee des Pakets ist folgende: Verarbeiten Sie die Eingabe, und wenn ein Buchstabe gefolgt von kombinierten Zeichen gefunden wird, wird dieser durch die normalisierte NFC-Form ersetzt. Es stehen zwei Methoden zur Verfügung. Mein erster Ansatz bestand darin, Knotenverarbeitungs-Rückrufe zu verwenden, um zerlegte Glyphen durch normalisierte Zeichen zu ersetzen. Dies hätte den Vorteil, dass die Verarbeitung mithilfe von Knotenattributen überall ein- und ausgeschaltet werden könnte. Die andere mögliche Funktion könnte darin bestehen, zu prüfen, ob die aktuelle Schriftart normalisierte Zeichen enthält, und andernfalls die Originalform zu verwenden. Leider schlägt dies in meinen Tests bei einigen Zeichen fehl, insbesondere í
steht „zusammengesetzt“ in den Knoten als dotless i + ´
, statt als i + ´
, was nach der Normalisierung nicht das richtige Zeichen ergibt, sodass stattdessen „zusammengesetzte Zeichen“ verwendet werden. Dies erzeugt jedoch eine Ausgabe mit falscher Akzentplatzierung. Diese Methode muss also entweder korrigiert werden oder ist völlig falsch.
Die andere Methode besteht darin, Callback zu verwenden, process_input_buffer
um die Eingabedatei beim Lesen von der Festplatte zu normalisieren. Diese Methode erlaubt weder die Verwendung von Informationen aus Schriftarten noch das Ausschalten mitten in der Zeile, ist aber wesentlich einfacher zu implementieren. Die Callback-Funktion kann folgendermaßen aussehen:
function buffer_callback(line)
return NFC(line)
end
was ein wirklich schönes Ergebnis ist, nachdem man drei Tage mit der Version der Knotenverarbeitung verbracht hat.
Aus Neugier, dies ist das Lua-Paket:
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
und jetzt ist es Zeit für ein paar Bilder.
ohne Normalisierung:
Sie können sehen, dass zusammengesetzte griechische Zeichen falsch sind, andere Kombinationen werden von Linux Libertine unterstützt
mit Knotennormalisierung:
Griechische Buchstaben sind richtig, aber „ í
in first“ příliš
ist falsch. Das ist das Problem, von dem ich gesprochen habe.
und nun die Puffernormalisierung:
alles ist jetzt in Ordnung