KonghaYao / cn-font-split

划时代的字体切割工具,CJK与任何字符!支持 otf、ttf、woff2 字体多线程切割,完美地细颗粒度地进行包大小控制。A revolutionary font subetter that supports CJK and any characters! It enables multi-threaded subset of otf, ttf, and woff2 fonts, allowing for precise control over package size.
https://chinese-font.netlify.app/
Apache License 2.0
496 stars 16 forks source link

[feature]: 提供分包策略性能评估标准 #56

Closed KonghaYao closed 4 months ago

KonghaYao commented 5 months ago

目的: 为了持续优化和算法的稳定迭代,需要设定针对于分包的切分方案,保证程序质量

  1. 分包字符使用率:文本独立字符总数/分包所包含总数
  2. 分包加载率:分包数据大小 * 分包个数 (得分越小越好)
i18nsite commented 4 months ago

分享下我用的切片方案

中文字符字频统计 https://github.com/i18n-now/cncount/blob/main/count.json

切分分组 https://github.com/i18n-now/cncount/blob/main/split.json

统计了wiki和nlp_chinese_corpus

image

切分代码 https://github.com/i18n-site/font/blob/main/gen.coffee

image

另外汇报2个bug

就是设置了css:{fontFamily:'s'}在生成的html不会用这个设置的fontFamily,还会用原来的字体名

还有,就是貌似不是严格按照subsets来分包的,会添加一些奇怪的字符,比如下面第一组切片

image

我仔细检查了split.json,没有这种东西啊

i18nsite commented 4 months ago

在我自己的网页上做了一个测试,

默认方案是4.8MB @import "//cdn.jsdelivr.net/npm/18x@0.1.147/_.css"

上面split.json的字频方案是3.9MB @import "//cdn.jsdelivr.net/npm/18x@0.1.148/_.css"

字体用到了 h 和 s

image image
KonghaYao commented 4 months ago

autoChunk: false 才会严格按照 subsets 进行分包。 至于分包方案的性能测试,会稍后安排

i18nsite commented 4 months ago

autoChunk: false 才会严格按照 subsets 进行分包。 至于分包方案的性能测试,会稍后安排

如果我的subsets字符覆盖不全,设置autoChunk:false 是不是会漏掉一部分字符?

我觉得除了常用字符(我的split.json只生成了前面一万多个常用字符),其他字符按unicode区间划段就可以了,这样生成的css文件应该会小一点,因为我看unicode区间可以简写

另外评测的时候,应该是woff加载的大小+css的大小,虽然css看起来压缩后并不大

但是我不知道怎么实现这个需求。我看unicode完整区间是 U+0025-00FF, 但是我不知道怎么划分让每个区间包含的字符数差不多,cn-font-split也没暴露这个相关的接口

如果unicode区间有重复,浏览器会默认加载第一个还是最后一个?这个我也不确定,望指教。如果按生成常用字符和unicode区间css简写的方案,感觉会涉及这个重复区间优先级的问题

如果想避免重复倒也是可以,可以用下面这个打洞的算法,

写js 函数 split_li(begin, end, hole_li), 输入参数 1,6,[2,5], 返回 [1,[3,4],6], 也就是把一个连续区间打洞,返回剩余的点和区间,代码要简洁优雅用中文注释

我用chatgpt写的,没仔细检查,但是输出是对的 https://chatgpt.com/share/728cf261-66b3-41d2-8340-7439801a33e8

function split_li(begin, end, hole_li) {
    // 确保 hole_li 按升序排序
    hole_li.sort((a, b) => a - b);

    // 初始化结果数组
    let result = [];

    // 起始位置
    let current = begin;

    // 遍历 hole_li
    for (let i = 0; i < hole_li.length; i++) {
        let hole = hole_li[i];

        // 确保 hole 在有效范围内
        if (hole < begin ) continue;
if(hole > end)  break;
        // 如果 current 到 hole-1 之间有区间,加入结果
        if (current < hole) {
            result.push(current === hole - 1 ? current : [current, hole - 1]);
        }

        // 更新 current 为 hole 的下一个位置
        current = hole + 1;
    }

    // 如果 current 到 end 之间有区间,加入结果
    if (current <= end) {
        result.push(current === end ? current : [current, end]);
    }

    return result;
}

// 测试
console.log(split_li(1, 6, [2, 5])); // 输出 [1, [3, 4], 6]
i18nsite commented 4 months ago

现在有接口可以拿到字体文件中有哪些字符吗?如果可以,再加上提供一个unicode区间(可以多个区间一组、也可以同时有单个字符)的分片的接口,我想可以自定义出更好的分包策略

比如 subsets 支持 输入数组 [[0x0025,0x00ff],[0x00ff,0x01ff],0x02ff] 这样

KonghaYao commented 4 months ago

其它需求请新开 issues 处理哈 @i18n-now

i18nsite commented 4 months ago

明白, 我已经实现了完全自己来切分字体 代码 https://github.com/i18n-site/font/blob/main/subset.coffee 切分方案如下

#!/usr/bin/env coffee

> fontkit > openSync
  fs > existsSync
  path > join
  opentype.js > load
  ./top10000.js

ROOT = import.meta.dirname
TOP10000 = [...top10000].map (i)=> i.charCodeAt 0
IGNORE = new Set TOP10000
cmp = (a, b) => a - b

export default main = (ttf, cutnum)=>
  ttf = await load(ttf)
  exist = new Set

  unicodes = new Set

  for [key, val] from Object.entries ttf.glyphs.glyphs
    for i from val.unicodes
      if IGNORE.has i
        exist.add i
      else
        unicodes.add i

  unicodes = [...unicodes]

  unicodes.sort(cmp)

  li = []
  t = []
  for i from unicodes
    t.push i
    if t.length >= cutnum
      li.push t
      t = []

  if t.length
    if li.length
      end = li.at(-1)
      while end.length > t.length
        t.push(end.pop())
      t.sort(cmp)
    li.push t

  t = []
  for i from TOP10000
    if not exist.has i
      continue
    t.push i
    if t.length >= cutnum
      t.sort(cmp)
      li.push t
      t = []

  if t.length
    if li.length
      end = li.at(-1)
      while end.length > t.length
        t.push(end.pop())
    t.sort(cmp)
    li.push t

  return li
KonghaYao commented 4 months ago
image image

您好,针对于您的分包方案,这边使用 2000 条中等长度的数据集进行了测试,测试结果如上。 其中,您的分包方案在同等情况下,将会导致请求总文件上升大约 100 KB,但是可以明显减少请求的并发数量,这或许就是您所需要的情况。 减少并发数量则是建立在提高单个包的体积的情况下的,平均单包大小超过了 100KB 将会导致网络情况不佳情况下单次连接时常过长,延长首屏加载时间,这个则是不建议采用的。 @i18n-now

i18nsite commented 4 months ago

怎么运行这个测试

https://cloud.tencent.com/developer/article/1803370 貌似浏览器对单个域名的并发请求中上限是6。 我不确定http2多路复用是不是可以突破这个限制,需要再研究 应该基于这个请求并发上限,和带宽,估算响应延时。 并且,现实中除了字体,其实还有其他css、js文件要加载。

具体评测应该在相同的并发数的情况下评测,我可以调整一下分包的大小参数,上面函数的cutnum。

除此之外,还应该加上每个请求的请求头和响应头的大小才比较合理 我看了一下,每个请求的额外请求头开销大概是1.6k(不过根据cloudflare数据),应该请求头会压缩掉76%,响应头会压缩掉 69%,估算下也每个请求的额外开销是458字节

:authority: registry.npmmirror.com
:method: GET
:path: /fc3/0.1.8/files/fB.woff2
:scheme: https
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en;q=0.6
Cache-Control: no-cache
Origin: https://127.0.0.1:7778
Pragma: no-cache
Priority: u=0
Referer: https://registry.npmmirror.com/fc3/0.1.8/files/index.css
Sec-Ch-Ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: font
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
HTTP/2 200
server: Tengine
content-type: font/woff2
strict-transport-security: max-age=5184000
date: Sat, 15 Jun 2024 01:23:36 GMT
vary: Origin, Accept, Accept-Encoding
access-control-allow-origin: https://127.0.0.1:7778
access-control-allow-credentials: true
request-id: e390f5b0-2ab5-11ef-a9d5-a346163075c6
cache-control: public, max-age=31536000
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
x-download-options: noopen
x-readtime: 96.095
via: cn171.l1, kunlun10.cn171, l2cn3135.l2, cache63.l2cn3135, npmmirror-x86-2022012802003, cache63.l2cn3135[138,137,200-0,M], cache77.l2cn3135[138,0], kunlun10.cn171[213,212,200-0,M], kunlun10.cn171[214,0]
ali-swift-global-savetime: 1718414616
age: 0
x-cache: MISS TCP_MISS dirn:-2:-2
x-swift-savetime: Sat, 15 Jun 2024 01:23:36 GMT
x-swift-cachetime: 31536000
timing-allow-origin: *
eagleid: ddb5c8a017184146166878601e
i18nsite commented 4 months ago

我又发了一个新的包,请基于这个再测试下看看? @KonghaYao https://cdn.jsdelivr.net/npm/18x@0.1.152/_.css

字体文件

https://registry.npmmirror.com/fc3/0.1.11/files/index.css

我对比了下,加载大小从上面的3.9MB降低到了3MB

image

(我后来又合并了同字形,下降到了2.9MB)

文件大小如下


    文件已下载完成;不会进行任何操作。

+ rsync -av woff2/ tmp
+ cd /Users/z/i18n/font
+ bun x webpack --mode production
+ rm -rf lib/main.js
To github.com:i18n-site/font.git
   a2eaf69..e7139e0  dev -> dev
To atomgit.com:i18n/font.git
   a2eaf69..e7139e0  dev -> dev
npm notice
npm notice 📦  fc3@0.1.9
npm notice Tarball Contents
npm notice 165.7kB _.woff2
npm notice 5.4kB _f.woff2
npm notice 102.2kB _s.woff2
npm notice 161.2kB -.woff2
npm notice 75.2kB -9.woff2
npm notice 208.8kB -H.woff2
npm notice 38.1kB 0.woff2
npm notice 32.6kB 0Z.woff2
npm notice 147.9kB 1.woff2
npm notice 115.8kB 1T.woff2
npm notice 113.3kB 1V.woff2
npm notice 41.0kB 1y.woff2
npm notice 173.9kB 2.woff2
npm notice 151.9kB 2X.woff2
npm notice 163.6kB 3.woff2
npm notice 144.0kB 3b.woff2
npm notice 43.2kB 3e.woff2
npm notice 173.1kB 3h.woff2
npm notice 93.9kB 3P.woff2
npm notice 42.9kB 3W.woff2
npm notice 158.4kB 3Y.woff2
npm notice 110.4kB 4.woff2
npm notice 162.2kB 4S.woff2
npm notice 155.7kB 5.woff2
npm notice 172.3kB 5i.woff2
npm notice 69.4kB 5V.woff2
npm notice 35.5kB 6.woff2
npm notice 164.9kB 7.woff2
npm notice 35.9kB 7c.woff2
npm notice 162.2kB 8.woff2
npm notice 38.2kB 8F.woff2
npm notice 133.4kB 8o.woff2
npm notice 164.2kB 8q.woff2
npm notice 168.1kB 9.woff2
npm notice 115.9kB 9b.woff2
npm notice 39.5kB 9F.woff2
npm notice 143.5kB 9fF.woff2
npm notice 23.3kB 9o.woff2
npm notice 31.9kB 79.woff2
npm notice 148.0kB 96.woff2
npm notice 110.3kB A.woff2
npm notice 39.5kB B.woff2
npm notice 182.5kB BX.woff2
npm notice 136.6kB D0.woff2
npm notice 124.1kB DN.woff2
npm notice 37.0kB Eb.woff2
npm notice 178.6kB ED.woff2
npm notice 165.0kB EUV.woff2
npm notice 114.2kB F.woff2
npm notice 37.9kB Fn.woff2
npm notice 169.2kB G.woff2
npm notice 143.4kB GQ.woff2
npm notice 35.0kB H.woff2
npm notice 132.2kB H0.woff2
npm notice 180.5kB H1.woff2
npm notice 197.7kB HC.woff2
npm notice 130.0kB HP.woff2
npm notice 158.4kB I.woff2
npm notice 7.8kB ICo.woff2
npm notice 166.4kB ID.woff2
npm notice 169.1kB J.woff2
npm notice 6.6kB J8.woff2
npm notice 125.5kB JB8.woff2
npm notice 34.3kB Jh.woff2
npm notice 191.6kB Ji.woff2
npm notice 39.4kB Js.woff2
npm notice 167.9kB K2.woff2
npm notice 34.2kB KD.woff2
npm notice 227.7kB LX.woff2
npm notice 171.9kB N.woff2
npm notice 166.6kB NKf.woff2
npm notice 170.7kB Nl.woff2
npm notice 37.2kB O.woff2
npm notice 171.4kB Oe.woff2
npm notice 155.2kB Om.woff2
npm notice 36.4kB P.woff2
npm notice 148.4kB Q3.woff2
npm notice 131.6kB Qa.woff2
npm notice 44.5kB QD.woff2
npm notice 231.4kB Qi.woff2
npm notice 3.5kB README.md
npm notice 113.5kB Rk.woff2
npm notice 144.5kB Rq.woff2
npm notice 37.5kB S.woff2
npm notice 39.2kB SV.woff2
npm notice 170.8kB T7.woff2
npm notice 168.2kB TmW.woff2
npm notice 131.3kB TT.woff2
npm notice 84.4kB TU.woff2
npm notice 111.4kB Tx.woff2
npm notice 170.9kB Us.woff2
npm notice 162.4kB W.woff2
npm notice 141.0kB W7.woff2
npm notice 121.1kB Wb.woff2
npm notice 42.6kB Wd.woff2
npm notice 159.3kB Wh.woff2
npm notice 105.5kB X3.woff2
npm notice 118.6kB X7.woff2
npm notice 35.5kB XB.woff2
npm notice 34.7kB Xr.woff2
npm notice 152.7kB YE.woff2
npm notice 168.5kB Z.woff2
npm notice 171.3kB ZN.woff2
npm notice 42.7kB ac.woff2
npm notice 38.6kB c.woff2
npm notice 114.8kB cc.woff2
npm notice 153.0kB cT.woff2
npm notice 190.8kB cu.woff2
npm notice 181.1kB d.woff2
npm notice 180.1kB dD.woff2
npm notice 36.6kB dF.woff2
npm notice 164.1kB e.woff2
npm notice 177.9kB eU.woff2
npm notice 155.6kB fi.woff2
npm notice 93.5kB gn.woff2
npm notice 166.8kB gv.woff2
npm notice 167.0kB hd.woff2
npm notice 107.2kB hr.woff2
npm notice 128.4kB iC.woff2
npm notice 200.9kB index.css
npm notice 35.1kB io.woff2
npm notice 172.1kB iP.woff2
npm notice 164.2kB ix.woff2
npm notice 186.2kB jb.woff2
npm notice 162.1kB jM.woff2
npm notice 107.0kB jq.woff2
npm notice 166.9kB jse.woff2
npm notice 151.8kB jso.woff2
npm notice 105.1kB k.woff2
npm notice 108.3kB kj.woff2
npm notice 105.9kB kjN.woff2
npm notice 130.7kB kU.woff2
npm notice 33.1kB l.woff2
npm notice 199.8kB l3.woff2
npm notice 144.1kB lA.woff2
npm notice 36.0kB lb.woff2
npm notice 188.5kB lC.woff2
npm notice 35.0kB ld.woff2
npm notice 107.1kB lm.woff2
npm notice 40.3kB lO.woff2
npm notice 124.5kB lr.woff2
npm notice 73.0kB lu.woff2
npm notice 171.7kB m.woff2
npm notice 165.2kB n3.woff2
npm notice 43.9kB nK.woff2
npm notice 172.8kB nl6.woff2
npm notice 317B package.json
npm notice 33.9kB pk.woff2
npm notice 41.0kB pL.woff2
npm notice 172.4kB q.woff2
npm notice 211.8kB ql.woff2
npm notice 171.6kB qN.woff2
npm notice 137.9kB qX.woff2
npm notice 165.4kB r.woff2
npm notice 195.1kB rs.woff2
npm notice 162.9kB rV.woff2
npm notice 150.1kB sH.woff2
npm notice 154.4kB t.woff2
npm notice 168.5kB t0.woff2
npm notice 102.5kB t2.woff2
npm notice 172.2kB tm.woff2
npm notice 195.9kB u.woff2
npm notice 104.2kB uy.woff2
npm notice 11.9kB uz.woff2
npm notice 131.9kB v.woff2
npm notice 118.8kB wp.woff2
npm notice 174.1kB x.woff2
npm notice 186.9kB xBy.woff2
npm notice 103.5kB xg.woff2
npm notice 188.1kB xI.woff2
npm notice 187.1kB xP.woff2
npm notice 193.1kB xR4.woff2
npm notice 100.0kB xrL.woff2
npm notice 40.0kB xZ.woff2
npm notice 36.3kB y.woff2
npm notice 180.8kB y1.woff2
npm notice 176.6kB y8.woff2
npm notice 160.1kB yQ.woff2
npm notice 7.8kB yW.woff2
npm notice 65.1kB zI.woff2
npm notice 115.2kB zK.woff2
npm notice 126.5kB zp.woff2
npm notice 113.8kB zt.woff2
npm notice 40.3kB zv.woff2
npm notice Tarball Details
npm notice name: fc3
npm notice version: 0.1.9
npm notice filename: fc3-0.1.9.tgz
npm notice package size: 22.0 MB
npm notice unpacked size: 22.2 MB
npm notice shasum: 5fd20bf4f95b6338feb382aef380c03bec94d1d3
npm notice integrity: sha512-ISlql7GmlzRRF[...]frE1s4JJw/ZDg==
npm notice total files: 184
npm notice