build 配置

字体 build 配置有三个基本层:

  1. 通用基础架构和元数据
  2. 可重复使用的字体包
  3. 产品专属配置

背景

在 Fuchsia build 中获取字体集合的设置有些棘手。这是因为

  • Fuchsia 的开发者指南倾向于将预构建的二进制文件保留在 CIPD 中,而不是 Git 代码库中。
  • Fuchsia 的字体文件来自各种来源。
  • 在运行中的 Fuchsia 系统上,仅向客户端提供一个包含 .ttf 文件的目录是不够的。
    • 字体文件并非完全自描述;有一些重要的元数据(例如名称、别名和样式属性)必须从外部获取。
    • 其他元数据(例如字体支持的码位列表)可以在运行时从每个字体文件中获取,但在构建时预先计算这些元数据会更高效。
    • 客户端应用可能不清楚自己要找的确切字体;它们可能想要请求特定的通用字体系列、样式和语言,但实际上并没有特定的字体文件。
  • 字体元数据冗长、经常重复,并且容易出错。最好为 Fuchsia 上提供的每个常用字体仅定义一次此元数据,而不是要求产品目标和各个组件的维护者管理自己的副本。

通用基础设施和元数据

第一层建立可用于 Fuchsia 产品 build 的资源和元数据目录。

字体数据代码库

字体文件的元数据和外部来源列表在字体数据代码库中维护。对于 Fuchsia 的开源字体,此文件位于 //fontdata。

每当字体数据代码库的内容发生变化时,Fuchsia 的自动化基础架构都会自动检出该代码库,提取该代码库描述的所有字体文件,并将其打包成 CIPD 软件包,然后上传到 Fuchsia 的 CIPD 服务器。

字体数据代码库包含以下文件:

manifest.xml

这是一个 Jiri 清单文件。在本例中,它由包含所需字体文件的 Git 代码库和修订版本 ID 列表组成。

每个导入的代码库都列在 <project> 元素中。

示例条目:

<project
    name="github.com/googlefonts/noto-cjk"
    path="github.com/googlefonts/noto-cjk"
    remote="https://fuchsia.googlesource.com/third_party/github.com/googlefonts/noto-cjk"
    revision="be6c059ac1587e556e2412b27f5155c8eb3ddbe6"/>
  • name:任意名称(不会影响任何内容)
  • path:确定相对于根检出目录,代码库的检出位置。
  • remote:远程代码库的 Git 网址
  • revision:要将检出固定到的 Git 提交哈希

contents.json

这些是 Fuchsia 基础架构将文件从检出目录复制到暂存目录的说明。{destination, files} 的每个条目都定义了暂存目录中的一个顶级文件夹,以及将复制到该文件夹中的文件列表。

示例条目:

{
    "destination": "material",
    "files": [
      "github.com/google/material-design-icons/iconfont/MaterialIcons-Regular.ttf",
      "github.com/google/material-design-icons/LICENSE"
    ]
}

${catalog_name}.font_catalog.json

字体目录文件是人工编写的元数据包,其中包含此字体数据代码库中的所有字体系列、资源和字体。

为便于修改,.font_catalog.json 文件的 JSON 架构位于 /src/fonts/tools/schemas/font_catalog.schema.json。规范化架构在 Rust 中定义。

packing_script.py

Fuchsia 的基础架构会在填充暂存目录后调用此脚本。

脚本:

通常无需修改该脚本。

基础设施方案

如上所述,每当 fontdata 被修改时,系统都会自动触发基础架构配方。此方案:

  1. 使用 jiri init 和 jiri update 检出引用的代码库。
  2. 根据 contents.json 暂存检出的文件。
  3. 调用打包脚本,将文件写入输出目录。
  4. 将输出目录的内容上传到 CIPD。

CIPD 软件包

开源字体软件包会上传到 fuchsia/third_party/fonts CIPD 软件包。

Jiri 预构建

CIPD 软件包的内容通过 prebuilts jiri 清单进入 Fuchsia 检出:

    <!--   Fonts -->
    <package name="fuchsia/third_party/fonts"
             version="git_revision:a10ce51e308f48c5e6d70ab5777434af46a5ddb8"
             path="prebuilt/third_party/fonts"/>

fuchsia/third_party/fonts 软件包的 version ID 可从 CIPD 软件包页面上最新实例的“Tags”中获取。

fuchsia/third_party/fonts CIPD 软件包的屏幕截图,其中突出显示了实例的标记,该标记以前缀“git_revision:”开头

全局 GN 实参

//src/fonts/build/font_args.gni 声明了多个与字体相关的 build 参数。其中最重要的两个是

*.font_pkgs.json 个文件

这些文件旨在由 GN 的简单 JSON 解析器 read_file 进行解析。它们包含以下形式的条目列表

    {
      "file_name": "AlphaSans-Regular.ttf",
      "safe_name": "alphasans-regular-ttf",
      "path_prefix": "alpha"
    },
  • file_name:字体文件资源文件的名称。这是每个字体资源的规范标识符,因此也是此对照表中的键。
  • safe_name:文件名的转换版本:转换为小写,所有特殊字符均替换为连字符。这可用于为 Fuchsia 软件包命名。
  • path_prefix:素材资源父级目录相对于 //prebuilt/third_party/fonts 的位置。

在 //src/fonts/build/fonts.gni 中,.font_pkgs.json 文件的内容会合并到一个可用作对照表的 GN 作用域中。

字体包

除了直接向字体提供程序的命名空间提供字体资源之外,还可以选择创建单字体 Fuchsia 软件包。顾名思义,package 的 resources 中仅包含一种字体,不包含其他任何内容。

这可与 fuchsia.pkg.FontResolver 结合使用,用于临时字体传送。(待办事项:链接到更多文档。)

所有可能的字体包都会在 //src/fonts/packages/BUILD.gn 中预先声明。

  • 每个软件包名称都采用 font-package-<font-safe-name> 格式(请参阅上文中的 safe_name),例如 font-package-roboto-regular-ttf。
  • 因此,软件包的 GN 目标为 //src/fonts/packages:<package-name>,例如 //src/fonts/packages:font-package-roboto-regular.ttf。
  • 每个软件包的网址格式均为 fuchsia-pkg://fuchsia.com/<package-name>,例如 fuchsia-pkg://fuchsia.com/font-package-roboto-regular-ttf。

local_font_bundle

在 fonts.gni 中定义

本地字体包是一组字体资源,这些资源使用 config_data() 直接放入字体提供程序的命名空间中。

大多数本地字体包都在 //src/fonts/collections/BUILD.gn 中声明。

示例:

local_font_bundle("small-open-fonts-local") {
  asset_names = [
    "MaterialIcons-Regular.ttf",
    "Roboto-Regular.ttf",
    "Roboto-Light.ttf",
    "Roboto-Medium.ttf",
    "RobotoMono-Regular.ttf",
    "RobotoSlab-Regular.ttf",
  ]
}

特定产品的字体配置

最后,每个使用字体的产品目标都需要配置其将包含的特定字体资源和元数据。

字体产品配置文件

.fontcfg.json(或 .fontcfg.json5)文件包含一组由人工编写的产品专用字体设置。JSON 架构可提供更好的编辑器体验。

目前,此文件的主要用途是定义特定的字体回退链,即当客户端的字体请求没有完全匹配项时,首选使用的字体序列。

以下是 //src/fonts/collections/open-fonts-collection.fontcfg.json5 中后备广告链的基本示例:

fallback_chain: [
        ///
        ///
        /// SANS SERIF LATIN
        "Roboto-Regular.ttf",
        "Roboto-Black.ttf",
        "Roboto-BlackItalic.ttf",
        "Roboto-Bold.ttf",
        "Roboto-BoldItalic.ttf",
        "Roboto-Italic.ttf",
        "Roboto-Light.ttf",
        "Roboto-LightItalic.ttf",
        "Roboto-Medium.ttf",
        "Roboto-MediumItalic.ttf",
        "Roboto-Thin.ttf",
        "Roboto-ThinItalic.ttf",
        "RobotoCondensed-Regular.ttf",
        "RobotoCondensed-Bold.ttf",
        "RobotoCondensed-BoldItalic.ttf",
        "RobotoCondensed-Italic.ttf",
        "RobotoCondensed-Light.ttf",
        "RobotoCondensed-LightItalic.ttf",
        "DroidSans-Regular.ttf",
        "DroidSans-Bold.ttf",

        ///
        ///
        /// SANS-SERIF NON-LATIN
        "NotoNaskhArabicUI-Regular.ttf",
        "NotoSansArmenian-Regular.ttf",
        "NotoSansEthiopic-Regular.ttf",
        "NotoSansGeorgian-Regular.ttf",
        "NotoSansHebrew-Regular.ttf",
        "NotoSansThaiUI-Regular.ttf",

        // All Indian scripts should come after Devanagari, due to shared danda characters.
        "NotoSansDevanagariUI-Regular.ttf",
        "NotoSansBengaliUI-Regular.ttf",
        "NotoSansGujaratiUI-Regular.ttf",
        "NotoSansKannada-Regular.ttf",
        "NotoSansMalayalamUI-Regular.ttf",
        "NotoSansTamilUI-Regular.ttf",
        "NotoSansTelugu-Regular.ttf",

        ///
        ///
        /// SANS SERIF CJK
        {
            full_name: "Noto Sans CJK SC",
        },
        {
            full_name: "Noto Sans CJK TC",
        },
        {
            full_name: "Noto Sans CJK HK",
        },
        {
            full_name: "Noto Sans CJK KR",
        },
        {
            full_name: "Noto Sans CJK JP",
        },
        {
            full_name: "Noto Sans CJK SC Bold",
        },
        {
            full_name: "Noto Sans CJK TC Bold",
        },
        {
            full_name: "Noto Sans CJK HK Bold",
        },
        {
            full_name: "Noto Sans CJK KR Bold",
        },
        {
            full_name: "Noto Sans CJK JP Bold",
        },
        {
            full_name: "Noto Sans CJK SC Black",
        },
        {
            full_name: "Noto Sans CJK TC Black",
        },
        {
            full_name: "Noto Sans CJK HK Black",
        },
        {
            full_name: "Noto Sans CJK KR Black",
        },
        {
            full_name: "Noto Sans CJK JP Black",
        },
        {
            full_name: "Noto Sans CJK SC DemiLight",
        },
        {
            full_name: "Noto Sans CJK TC DemiLight",
        },
        {
            full_name: "Noto Sans CJK HK DemiLight",
        },
        {
            full_name: "Noto Sans CJK KR DemiLight",
        },
        {
            full_name: "Noto Sans CJK JP DemiLight",
        },
        {
            full_name: "Noto Sans CJK SC Light",
        },
        {
            full_name: "Noto Sans CJK TC Light",
        },
        {
            full_name: "Noto Sans CJK HK Light",
        },
        {
            full_name: "Noto Sans CJK KR Light",
        },
        {
            full_name: "Noto Sans CJK JP Light",
        },
        {
            full_name: "Noto Sans CJK SC Medium",
        },
        {
            full_name: "Noto Sans CJK TC Medium",
        },
        {
            full_name: "Noto Sans CJK HK Medium",
        },
        {
            full_name: "Noto Sans CJK KR Medium",
        },
        {
            full_name: "Noto Sans CJK JP Medium",
        },
        {
            full_name: "Noto Sans CJK SC Thin",
        },
        {
            full_name: "Noto Sans CJK TC Thin",
        },
        {
            full_name: "Noto Sans CJK HK Thin",
        },
        {
            full_name: "Noto Sans CJK KR Thin",
        },
        {
            full_name: "Noto Sans CJK JP Thin",
        },

        ///
        ///
        /// SERIF LATIN
        "RobotoSlab-Regular.ttf",
        "RobotoSlab-Bold.ttf",
        "RobotoSlab-Light.ttf",
        "RobotoSlab-Thin.ttf",
        "DroidSerif-Regular.ttf",
        "DroidSerif-Bold.ttf",
        "DroidSerif-BoldItalic.ttf",
        "DroidSerif-Italic.ttf",

        ///
        ///
        /// SERIF CJK
        {
            full_name: "Noto Serif CJK SC",
        },
        {
            full_name: "Noto Serif CJK TC",
        },
        {
            full_name: "Noto Serif CJK KR",
        },
        {
            full_name: "Noto Serif CJK JP",
        },
        {
            full_name: "Noto Serif CJK SC Bold",
        },
        {
            full_name: "Noto Serif CJK TC Bold",
        },
        {
            full_name: "Noto Serif CJK KR Bold",
        },
        {
            full_name: "Noto Serif CJK JP Bold",
        },
        {
            full_name: "Noto Serif CJK SC Black",
        },
        {
            full_name: "Noto Serif CJK TC Black",
        },
        {
            full_name: "Noto Serif CJK KR Black",
        },
        {
            full_name: "Noto Serif CJK JP Black",
        },
        {
            full_name: "Noto Serif CJK SC ExtraLight",
        },
        {
            full_name: "Noto Serif CJK TC ExtraLight",
        },
        {
            full_name: "Noto Serif CJK KR ExtraLight",
        },
        {
            full_name: "Noto Serif CJK JP ExtraLight",
        },
        {
            full_name: "Noto Serif CJK SC Light",
        },
        {
            full_name: "Noto Serif CJK TC Light",
        },
        {
            full_name: "Noto Serif CJK KR Light",
        },
        {
            full_name: "Noto Serif CJK JP Light",
        },
        {
            full_name: "Noto Serif CJK SC Medium",
        },
        {
            full_name: "Noto Serif CJK TC Medium",
        },
        {
            full_name: "Noto Serif CJK KR Medium",
        },
        {
            full_name: "Noto Serif CJK JP Medium",
        },
        {
            full_name: "Noto Serif CJK SC SemiBold",
        },
        {
            full_name: "Noto Serif CJK TC SemiBold",
        },
        {
            full_name: "Noto Serif CJK KR SemiBold",
        },
        {
            full_name: "Noto Serif CJK JP SemiBold",
        },

        ///
        ///
        /// MONOSPACE LATIN
        "RobotoMono-Bold.ttf",
        "RobotoMono-BoldItalic.ttf",
        "RobotoMono-Italic.ttf",
        "RobotoMono-Light.ttf",
        "RobotoMono-LightItalic.ttf",
        "RobotoMono-Regular.ttf",
        "RobotoMono-Medium.ttf",
        "RobotoMono-MediumItalic.ttf",
        "RobotoMono-Thin.ttf",
        "RobotoMono-ThinItalic.ttf",
        "DroidSansMono-Regular.ttf",

        ///
        ///
        /// MONOSPACE CJK
        {
            full_name: "Noto Sans Mono CJK SC",
        },
        {
            full_name: "Noto Sans Mono CJK TC",
        },
        {
            full_name: "Noto Sans Mono CJK HK",
        },
        {
            full_name: "Noto Sans Mono CJK KR",
        },
        {
            full_name: "Noto Sans Mono CJK JP",
        },
        {
            full_name: "Noto Sans Mono CJK SC Bold",
        },
        {
            full_name: "Noto Sans Mono CJK TC Bold",
        },
        {
            full_name: "Noto Sans Mono CJK HK Bold",
        },
        {
            full_name: "Noto Sans Mono CJK KR Bold",
        },
        {
            full_name: "Noto Sans Mono CJK JP Bold",
        },

        ///
        ///
        /// CURSIVE LATIN
        "Quintessential-Regular.ttf",

        ///
        ///
        /// EMOJI
        "NotoColorEmoji.ttf",

        ///
        ///
        /// SYMBOLS
        "NotoSansSymbols-Regular.ttf",
        "NotoSansSymbols2-Regular.ttf",
    ]

如果资源文件包含多个字体,则可以使用 JSON 对象(而不仅仅是文件名)引用单个字体:

fallback_chain: [
    "SomeCompleteFont-Bold.ttf",
    { file_name: "NotoSansCJK-Regular.ttc", index: 1},
]

后备广告链是手动定义的。请遵循以下准则:

  • 为每种受支持的文字至少添加一种字体。(支持的脚本集因产品而异。)
  • 尝试至少涵盖 sans-serif、serif 和 monospace 字体系列。
  • 如果覆盖范围有重叠,请将更具体的素材资源放在列表中靠上的位置。例如,所有 Noto Sans 脚本专用字体都具有 ASCII 范围的字形,但 Noto Sans Latin 变体应排在第一位。
  • 如果覆盖范围有重叠,请将较小的字体文件放在列表中较高的位置。这样可以减少加载后备字体时的界面卡顿。

font_collection

在 fonts.gni 中定义

在声明任何所需的 local_font_bundle 和/或字体 packages 后,它们会被组装成 font_collection。

输入

(如需查看完整文档,请参阅 fonts.gni。)

  • font_packages:目标产品的 universe_package_labels 中字体 package 的 GN 标签。
  • local_font_bundles:目标产品的 local_font_bundle 的 GN 标签。这些证书将包含在字体提供程序的配置数据中。
  • local_asset_names:本地字体资源名称列表(创建临时 local_font_bundle)。
  • product_config_path:包含产品专用字体配置(包括后备链)的 JSON 文件的路径。
  • manifest_prefix:生成的字体清单文件名的前缀。 默认设置为 "all"。

内部原理

font_collection 会遍历其包含的所有字体资源和字体包的传递闭包。它会收集其 GN 元数据,以构建映射到本地文件中字体包的字体列表。

该模板会将此信息以及字体目录和所有字体资源的路径传递给字体清单生成器(GN 模板、Rust 源代码)。

清单生成器会从字体目录中选择 font_collection 中包含的字体文件的预定义元数据。它还会读取每个包含的字体文件,并提取每个字体文件支持的码位(字符集)列表。

所有这些数据都会汇编到一个 .font_manifest.json 文件中,该文件会使用 config_data 规则提供给字体提供程序服务。

输出

font_collection 会生成以下制品:

  • <manifest-prefix>.font_manifest.json:这是上述元数据的组合。它使用 config_data 规则提供给字体提供程序服务。
  • font_packages.json。此文件列出了所有包含的单字体 Fuchsia 软件包,并使用 config_data 规则提供给 pkg-resolver。这决定了通过 fuchsia.pkg.FontResolver 公开的字体包。

由 font_collection 创建的 GN 目标包含上述两个 JSON 文件的 config_data,以及本地字体资源的 config_data 目标。

如果产品仅使用本地字体,则只需将 font_collection 目标添加到产品的依赖项标签(通常为 base_package_labels)即可。

如果产品还使用字体 package,则必须将这些目标明确添加到 base_package_labels 或 universe_package_labels,具体取决于软件包是否为临时软件包。