LuaLaTeX 对后备字体使用什么算法?

LuaLaTeX 对后备字体使用什么算法?

背景 几年来,我一直在忙着写一本关于世界文字和语言的书。它涵盖了全部以 unicode 格式列出的脚本,以及十几个还没有 unicode 标准的脚本,我使用图像而不是字体。

随着 Noto 字体、unicode LuaLaTeX 和 l3 的成熟,我已经能够根据写作需要为所有脚本打印合理的范围。除了东亚脚本,每个脚本只有几页。我使用 Brill 作为主要字体,并添加了后备字体以覆盖其余脚本。到目前为止,这本书大约有 350 页,我预计最终将达到 600 页。要涵盖 unicode 点,字体需要提供 +-150,000 个字形。书中并未使用所有代码点,正如我之前提到的,据我估计,我只需要其中的一半左右。显然,编译速度是一个问题,可以理解,因此我希望了解 luaotfload-fallback.lua 使用的算法,以尝试看看我是否可以缩短处理时间。我正在寻找优化编译时间的策略,不仅针对我的文档,而且针对一般情况。

我发现瓶颈主要在三个方面:a) 字体 b) 图像 c) 日志记录(一般磁盘写入)。图像我将使用预处理器并优化所有图像以及生成 pdf。我将使用 Golang 作为预处理器,如果需要,它还可以进行标记。字体和日志记录的想法见下文。

  1. 我有一个(疯狂的)想法,即处理过程中节点所需的字形信息可以通过本地服务器获取,因此某些任务可以外部化并并发运行。我正在考虑某种形式的优先级队列,以便可以快速提供经常使用的代码点数据,并在第二次运行时将任何未使用的代码点从缓存中取出。这里我将再次使用 Golang 和 sqlite3,因为一切都是本地的。我目前有一个 Lua 表,它根据配置文件将 unicode 点映射到字体。

  2. 所有日志记录也将被发送到服务器,而不是写入磁盘。辅助文件也可以这样做。

  3. 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 Ξ Ж س
        क ௵ ෴ ფ
        ጄ ᑠ ᘟ Ⅶ
        ∰ ⡿ だ 㬯

相关内容