如何减少字体文件尺寸

本 blog 最初选定英文字体的时候选择了Great Vibes这个字体,相对之下中日文的粗体字就会显得很不搭,一直在考虑解决。如果只是指定serif的话在 mac 端上能够显示想要的效果但是其他客户端都不行。自然应该是系统自带字体库的问题,最简单方法就是再加载一个外部字体。中日文都支持的字体里最有名的应该是noto-serif-sc,但是因为字符数很多,文件将近 7.7Mb。就算初次加载之后有 cache,初次加载的 cost 也太大了。

一顿搜索之后找到了fonttools这个开源项目,其中提供的pyftsubset这个工具可以对字体文件进行裁剪取出需要的字符。可惜并没有找到 node 对应的项目。

$ pyftsubset --help
...
  --unicodes=<XXXX>[,<XXXX>...]
      Specify comma/whitespace-separated list of Unicode codepoints or
      ranges as hex numbers, optionally prefixed with 'U+', 'u', etc.
      For example, --unicodes=41-5a,61-7a adds ASCII letters, so does
      the more verbose --unicodes=U+0041-005A,U+0061-007A.
      The special strings '*' will choose all Unicode characters mapped
      by the font.
...

接下来使用gatsby的 query 获取所有的标题进行处理,在gatsby-node.js中加入prebuild的 hook:

query 如下:

{
  allMdx {
    edges {
      node {
        frontmatter {
          title
        }
      }
    }
  }
}

整理 unicode 字符串并外部执行 py 命令:

const unicodes = [...new Set(titles.join("").replace(/[a-zA-Z0-9\s]/g, ""))]
  .map(
    char =>
      "U+" +
      char
        .charCodeAt(0)
        .toString(16)
        .padStart(4, "0")
  )
  .join(",")

合并两者:

exports.onPreBuild = ({ graphql }) => {
  console.log('preBuild fonts!')
  return graphql(`
  {
    allMdx {
      edges {
        node {
          frontmatter {
            title
          }
        }
      }
    }
  }
`).then(result => {
    return new Promise(ok => {
      const titles = result.data.allMdx.edges.map(
        ({ node }) => node.frontmatter.title
      )

      const unicodes = [...new Set(titles.join("").replace(/[a-zA-Z0-9\s]/g, ""))]
        .map(
          char =>
            "U+" +
            char
              .charCodeAt(0)
              .toString(16)
              .padStart(4, "0")
        )
        .join(",")

      exec(
        `pyftsubset static/fonts/NotoSerifSC-Regular.woff2 --unicodes="${unicodes}" --flavor="woff2" `,
        (err) => {
          if (err) {
            throw err
          };
          ok();
      )
    }
    )
  })
}

现在再确认一眼生成的NotoSerifSC-Regular.subset.woff2只有 72kB,差距还是非常巨大的。