利用 LuaTeX 中新的 harfbuzz 字体引擎,我寻找一种基于字形的回退解决方案。
假设我有一个没有一些拉丁字母的 CJK 字体,我想选择 CJK 字体和我的拉丁字体作为后备。
目前我使用虚拟字体来实现这个目的。首先,我用拉丁字体中的每个字符填充虚拟字体,然后用 CJK 字体覆盖所有位置。
现在有了 LuaTeX 中的 harfbuzz 引擎,我认为这种方法不再需要/不可能。
这些是我目前在 LuaTeX 中使用 harfbuzz 的唯一步骤:
local face = hb.new_face(f.filename)
local font = hb.new_font(face)
local buf = hb.Buffer.new()
buf:add_utf8(str)
hb.shape(font, buf, { language = "...", script = "...", direction = "ltr" },"")
我可以采取什么字体回退或模拟虚拟字体的策略?
答案1
HarfBuzz 没有任何内置功能可以执行此操作,因此您必须手动执行。您使用一种拉丁字体和一种 CJK 字体的示例很常见,但它实际上结合了多个概念:不同的脚本和不同的字体。即使您只有一个包含所有字形的字体,您也必须先将文本拆分为拉丁部分和 CJK 部分,然后分别对其进行整形。我们暂时将忽略这一点,以避免将其变成有关 Unicode 脚本处理的答案。
因此,我们暂时假设所有字符都具有相同的字体,或者应使用 CJK 脚本塑造的字形正是拉丁字体中不包含的字形。(这种假设通常是错误的,例如由于常见的标点符号、数字等。)
首先,您需要知道每个字体覆盖了哪些代码点。根据您加载字体的方式,您可能在加载时为此创建了一个表。对于此步骤,face:collect_unicodes()
是您的朋友:它返回所有覆盖代码点的数组。
local face1 = hb.new_face(f1.filename)
local unicodes1 = face1:collect_unicodes()
local font1 = hb.new_font(face1)
local face2 = hb.new_face(f2.filename)
local unicodes2 = face2:collect_unicodes()
local font2 = hb.new_font(face2)
如果我们有一个包含所有覆盖代码点的 true 的表,那么使用起来会更容易,所以让我们对其进行转换:
local has_unicode1 = {}
for i=1,#unicodes1 do
has_unicode1[unicodes1[i]] = true
end
-- And the same for font2, omitted here
现在,您要遍历文本,确定每个字符由哪种字体覆盖。在这里,您必须决定如何处理未被任何字体覆盖的代码点。我们将只使用字体 1:
local lastfontchange = 1
local lastfont
for offset, codepoint in utf8.codes(str) do
local currentfont = has_unicode1[codepoint] and 1
or has_unicode2[codepoint] and 2
or 1
-- Did the font change?
if lastfont ~= currentfont then
if offset ~= 1 then -- We only have to do something if this isn't the first font we are dealing with
-- Now shape the part between `lastfontchange` and `offset`
local buf = hb.Buffer.new()
buf:add_utf8(str:sub(lastfontchange, offset-1))
if lastfont == 1 then
hb.shape(font1, buf, { language = "...", script = "...", direction = "ltr" },"")
else -- lastfont == 2 then
hb.shape(font2, buf, { language = "...", script = "...", direction = "ltr" },"")
end
-- TODO: Do something with the shaped glyphs
end
lastfontchange = offset
lastfont = currentfont
end
end
-- We still need to shape everything after the last font change (This part probably should be made into it's own function to avoid the repetition):
local buf = hb.Buffer.new()
buf:add_utf8(str:sub(lastfontchange, #str))
if lastfont == 1 then
hb.shape(font1, buf, { language = "...", script = "...", direction = "ltr" },"")
else -- lastfont == 2 then
hb.shape(font2, buf, { language = "...", script = "...", direction = "ltr" },"")
end
-- TODO: Do something with the shaped glyphs