背景 几年来,我一直在忙着写一本关于世界文字和语言的书。它涵盖了全部以 unicode 格式列出的脚本,以及十几个还没有 unicode 标准的脚本,我使用图像而不是字体。
随着 Noto 字体、unicode LuaLaTeX 和 l3 的成熟,我已经能够根据写作需要为所有脚本打印合理的范围。除了东亚脚本,每个脚本只有几页。我使用 Brill 作为主要字体,并添加了后备字体以覆盖其余脚本。到目前为止,这本书大约有 350 页,我预计最终将达到 600 页。要涵盖 unicode 点,字体需要提供 +-150,000 个字形。书中并未使用所有代码点,正如我之前提到的,据我估计,我只需要其中的一半左右。显然,编译速度是一个问题,可以理解,因此我希望了解 luaotfload-fallback.lua 使用的算法,以尝试看看我是否可以缩短处理时间。我正在寻找优化编译时间的策略,不仅针对我的文档,而且针对一般情况。
我发现瓶颈主要在三个方面:a) 字体 b) 图像 c) 日志记录(一般磁盘写入)。图像我将使用预处理器并优化所有图像以及生成 pdf。我将使用 Golang 作为预处理器,如果需要,它还可以进行标记。字体和日志记录的想法见下文。
我有一个(疯狂的)想法,即处理过程中节点所需的字形信息可以通过本地服务器获取,因此某些任务可以外部化并并发运行。我正在考虑某种形式的优先级队列,以便可以快速提供经常使用的代码点数据,并在第二次运行时将任何未使用的代码点从缓存中取出。这里我将再次使用 Golang 和 sqlite3,因为一切都是本地的。我目前有一个 Lua 表,它根据配置文件将 unicode 点映射到字体。
所有日志记录也将被发送到服务器,而不是写入磁盘。辅助文件也可以这样做。
pdf 的生成也需要时间,但目前我还不确定是否可以优化。目前的编译速度大约是每页 1.3 秒 + 初始 30-40 秒。
问题 有人能给我解释一下 luaotfload-fallback.lua 中的算法步骤吗?LuaTeX 在构建文档时何时以及如何使用它?什么时候需要字形信息?欢迎提出任何想法。感谢您读到这里。
答案1
这根本没有回答问题标题中的问题,但我认为这解决了问题主体中提出的问题(希望如此)。
间接答案
这是一个使用 LuaLaTeX 在 7.505 秒内(平均)加载 231 种独特字体并打印 83 020 个独特字符(103 页)的解决方案。
首先,运行此脚本来下载所有字体:
#!/bin/sh
set -eu
mkdir fonts
cd fonts
git clone --depth 1 --no-checkout --filter=blob:none \
https://github.com/notofonts/notofonts.github.io.git
cd notofonts.github.io
git sparse-checkout set --no-cone '!/*' '/fonts/**/hinted/ttf/*-Regular.ttf'
git checkout main
cd ..
git clone --depth 1 --no-checkout --filter=blob:none \
https://github.com/notofonts/noto-cjk.git
cd noto-cjk
git sparse-checkout set --no-cone '!/*' '/Serif/SubsetOTF/**/*-Regular.otf'
git checkout main
cd ..
wget -O unifont-Regular.otf \
https://unifoundry.com/pub/unifont/unifont-15.1.04/font-builds/unifont-15.1.04.otf
wget -O unifont_upper-Regular.otf \
https://unifoundry.com/pub/unifont/unifont-15.1.04/font-builds/unifont_upper-15.1.04.otf
wget -O NotoEmoji-Regular.ttf \
"$(curl 'https://fonts.googleapis.com/css2?family=Noto+Emoji' | grep -o 'https.*ttf')"
cd ..
然后,将以下内容放入all-characters.lua
:
-- Save some globals for speed
local ipairs = ipairs
local max = math.max
local new_node = node.new
local node_write = node.write
local pairs = pairs
-- Define some constants
local GLUE_ID = node.id("glue")
local GLYPH_ID = node.id("glyph")
local SIZE = tex.sp("10pt")
-- Get all the fonts
local fontpaths = dir.glob("**-Regular.*", "./fonts")
-- Sort the fonts such that the "preferred" fonts are last
table.sort(fontpaths, function(a, b)
local a = file.nameonly(a):match("(.+)-Regular")
local b = file.nameonly(b):match("(.+)-Regular")
if a:match("Serif") and not b:match("Serif") then
return false
end
if b:match("Serif") and not a:match("Serif") then
return true
end
if a:match("unifont") and not b:match("unifont") then
return true
end
if b:match("unifont") and not a:match("unifont") then
return false
end
if #a == #b then
return a > b
end
return #a > #b
end)
-- Create a mapping from codepoint to font id
local by_character = {}
local virtual_fonts = {}
for _, filename in ipairs(fontpaths) do
local fontdata = fonts.definers.read {
lookup = "file",
name = filename,
size = SIZE,
features = {},
}
local id = font.define(fontdata)
fonts.definers.register(fontdata, id)
virtual_fonts[#virtual_fonts + 1] = { id = id }
for codepoint, char in pairs(fontdata.characters) do
if char.unicode == codepoint then
by_character[codepoint] = {
width = char.width,
height = char.height,
depth = char.depth,
font = id,
commands = {
{ "slot", #virtual_fonts, codepoint }
},
}
end
end
end
local function print_all_chars()
local count = 0
tex.forcehmode()
for codepoint, data in table.sortedpairs(by_character) do
local glyph = new_node(GLYPH_ID)
glyph.font = data.font
glyph.char = codepoint
local space = new_node(GLUE_ID)
space.width = max(2 * SIZE - glyph.width, 0)
glyph.next = space
node_write(glyph)
count = count + 1
end
tex.sprint("\\par Characters: " .. count)
tex.sprint("\\par Fonts: " .. #virtual_fonts)
end
-- Make the virtual font
local id = font.define {
name = "all-characters",
parameters = {},
characters = by_character,
properties = {},
type = "virtual",
fonts = virtual_fonts,
}
local new_command
if ltx then
new_command = function(name, func)
local index = luatexbase.new_luafunction(name)
lua.get_functions_table()[index] = func
token.set_lua(name, index, "protected")
end
elseif context then
new_command = function(name, func)
interfaces.implement {
name = name,
actions = func,
public = true,
}
end
end
new_command("printallchars", print_all_chars)
new_command("allcharactersfont", function() font.current(id) end)
然后,您可以使用以下文档打印所有字符:
\documentclass{article}
\ExplSyntaxOn
\lua_load_module:n { all-characters }
\ExplSyntaxOn
\begin{document}
\printallchars
\end{document}
ConTeXt 平均速度快 50%,为 4.849 秒:
\ctxloadluafile{all-characters}
\starttext
\printallchars
\stoptext
更有用的是,这还定义了一个\allcharactersfont
包含所有加载字体的字符的虚拟字体:
\documentclass{article}
\pagestyle{empty}
\ExplSyntaxOn
\lua_load_module:n { all-characters }
\ExplSyntaxOn
\begin{document}
{\allcharactersfont
A Ξ Ж س
क ௵ ෴ ფ
ጄ ᑠ ᘟ Ⅶ
∰ ⡿ だ 㬯
䷥