<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/assets/rss-20b3285f.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>标签: blog - ouuan's blog</title>
        <link>https://ouuan.moe/tag/blog</link>
        <description>标签为 blog 的文章 - ouuan 的博客</description>
        <lastBuildDate>Thu, 09 Feb 2023 15:19:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>Copyright © 2022 - 2026 ouuan
Licensed under CC BY-SA 4.0</copyright>
        <atom:link href="https://ouuan.moe/tag/blog/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[一些博客排版优化小 trick]]></title>
            <link>https://ouuan.moe/post/2023/02/typography-tricks</link>
            <guid>https://ouuan.moe/post/2023/02/typography-tricks</guid>
            <pubDate>Thu, 09 Feb 2023 15:19:45 GMT</pubDate>
            <description><![CDATA[

<p>我的博客好像还用了不少奇怪的小 trick 来优化 <span class="mojikumi">（</span><s>hopefully</s><span class="mojikumi">）</span> 排版<span class="mojikumi-line-end">，</span>在这整理一下<span class="mojikumi-line-end">，</span>分享出来<span class="mojikumi-line-end">，</span>也是怕几年<span class="mojikumi-line-start">（</span><s>几天</s><span class="mojikumi-line-end">）</span>后想不起来这些奇怪的东西是在干什么<span class="mojikumi-line-end">。</span></p>
<p>主要是 text justify 相关和标点挤压相关<span class="mojikumi-line-end">。</span></p>
]]></description>
            <content:encoded><![CDATA[

<p>我的博客好像还用了不少奇怪的小 trick 来优化 <span class="mojikumi">（</span><s>hopefully</s><span class="mojikumi">）</span> 排版<span class="mojikumi-line-end">，</span>在这整理一下<span class="mojikumi-line-end">，</span>分享出来<span class="mojikumi-line-end">，</span>也是怕几年<span class="mojikumi-line-start">（</span><s>几天</s><span class="mojikumi-line-end">）</span>后想不起来这些奇怪的东西是在干什么<span class="mojikumi-line-end">。</span></p>
<p>主要是 text justify 相关和标点挤压相关<span class="mojikumi-line-end">。</span></p>

<p>我博客的 Markdown 渲染基于 <a href="https://github.com/unifiedjs/unified">unified</a><span class="mojikumi-line-end">，</span>所以下面的一些优化是以 unified 插件<span class="mojikumi-line-start">（</span>remark / remark-rehype / rehype 插件<span class="mojikumi-line-end">）</span>的形式实现的<span class="mojikumi-line-end">。</span></p>
<aside role="note" data-v-a2ab257f><div class="shadow-md rd-1 b-l-6 my-6 bg-orange-1 dark:bg-orange-9 b-orange" data-v-a2ab257f><div class="p-3 flex justify-between items-center" data-v-a2ab257f><h2 class="flex items-center gap-1 font-bold" data-v-a2ab257f><span class="text-5 i-mdi-alert-circle-outline text-orange" data-v-a2ab257f></span><!--v-if--><span data-v-a2ab257f>Warning</span></h2><!--v-if--></div><div class="overflow-auto rd-br-1 bg-card px-6 dark:bg-bghover" data-v-a2ab257f><p>为了直观地进行演示<span class="mojikumi-line-end">，</span>本文可能会在提到一个问题的段落通过设置文本和 CSS 特意制造出相应的问题<span class="mojikumi-line-end">，</span>并禁用对这个问题的处理措施以将问题展示出来<span class="mojikumi-line-end">。</span></p></div></div></aside>
<h2 id="text-justify-相关" class="heading"><a href="#text-justify-相关" class="heading-anchor" aria-label="章节： text justify 相关" tabindex="-1"></a><span>text justify 相关</span></h2>
<p>在每行的宽度略有差异时<span class="mojikumi-line-end">，</span><a href="https://www.w3.org/TR/2022/DNOTE-clreq-20221009/#line_adjustment">使用两端对齐会让中文排版更加美观</a><span class="mojikumi-line-end">，</span>这一般通过 <a href="https://developer.mozilla.org/docs/Web/CSS/text-align"><code>text-align</code> CSS 属性</a> 的 <code>justify</code> 选项实现<span class="mojikumi-line-end">。</span>但由于网页排版的不确定性<span class="mojikumi-line-end">，</span>每行的宽度可能不是略有差异而是有很大差异<span class="mojikumi-line-end">，</span>这时使用 text justify 就会造成过大的空隙<span class="mojikumi-line-end">，</span>所以需要避免出现过短的行<span class="mojikumi-line-end">，</span>或者在有必要时禁用 text justify<span class="mojikumi-line-end">。</span></p>
<h3 id="langzh-cn-对-text-justify-的影响" class="heading"><a href="#langzh-cn-对-text-justify-的影响" class="heading-anchor" aria-label="章节： lang=&#x22;zh-CN&#x22; 对 text justify 的影响" tabindex="-1"></a><span>lang="zh-CN" 对 text justify 的影响</span></h3>
<p>这个并不是 trick<span class="mojikumi-line-end">，</span>是基操<span class="mojikumi-line-end">，</span>但挺重要的<span class="mojikumi-line-end">，</span>还是写在这<span class="mojikumi-line-end">。</span></p>
<p>如果没有将 HTML 的 <a href="https://developer.mozilla.org/docs/Web/HTML/Global_attributes/lang"><code>lang</code> 属性</a> 设为中文<span class="mojikumi-line-end">，</span>在 Firefox 109<span class="mojikumi-line-start">（</span>以及其他版本 / 其他浏览器<span class="mojikumi-line-end">，</span>但不包括 Chrome 109<span class="mojikumi-line-end">）</span>中就会按照英文的规则<span class="mojikumi-line-end">，</span>将连续的汉字视作一个单词<span class="mojikumi-line-end">，</span>而只在词与词之间增大间距<span class="mojikumi-line-end">，</span>不改变汉字之间的间距<span class="mojikumi-line-end">，</span>如下所示<span class="mojikumi-line-end">：</span></p>
<p lang="en" style="margin: auto; width: 12em; border: gray solid 1px;">这是一个设置了 <code>lang="en"</code> 和固定宽度的段落<span class="mojikumi-line-end">，</span>这是一个 loooooooooooooooong word<span class="mojikumi-line-end">。</span></p>
<p><picture><img type="image/webp" srcset="/assets/firefox-text-justify-en.9aad62d5.webp" loading="lazy" src="/assets/firefox-text-justify-en.9aad62d5.webp" width="243" height="162" alt="上面的 lang 设为 en 的段落在 Firefox 109 中的渲染效果"></picture></p>
<p>其中<span class="mojikumi-line-end">，</span>第一行没有两端对齐<span class="mojikumi-line-end">，</span>而第三行只在逗号后面增加了间距<span class="mojikumi-line-end">。</span></p>
<p>如果设置了 <code>lang<wbr>="<wbr>zh<wbr>-<wbr>CN<wbr>"</code> 则会在汉字间添加间距<span class="mojikumi-line-end">：</span></p>
<p lang="zh-CN" style="margin: auto; width: 12em; border: gray solid 1px;">这是一个设置了 <code>lang="zh-CN"</code> 和固定宽度的段落<span class="mojikumi-line-end">，</span>这是一个 loooooooooooooooong word<span class="mojikumi-line-end">。</span></p>
<p><picture><img type="image/webp" srcset="/assets/firefox-text-justify-zh-CN.e85aebf7.webp" loading="lazy" src="/assets/firefox-text-justify-zh-CN.e85aebf7.webp" width="245" height="161" alt="上面的 lang 设为 zh-CN 的段落在 Firefox 109 中的渲染效果"></picture></p>
<p>当然<span class="mojikumi-line-end">，</span>设置 HTML 的 <code>lang</code> 属性还有其他作用<span class="mojikumi-line-end">，</span>不管怎么样总是得设一个的<span class="mojikumi-line-end">。</span></p>
<h3 id="断开过长的行内代码" class="heading"><a href="#断开过长的行内代码" class="heading-anchor" aria-label="章节： 断开过长的行内代码" tabindex="-1"></a><span>断开过长的行内代码</span></h3>
<p>有时候会遇到一些 <code style="word-break: normal;">veryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongInlineCode</code><span class="mojikumi-line-end">，</span>为了让它不断开<span class="mojikumi-line-end">，</span>前面的一行就会非常短<span class="mojikumi-line-end">，</span>justify 后间隙就非常大<span class="mojikumi-line-end">。</span>当然<span class="mojikumi-line-end">，</span>这样的问题不局限于行内代码<span class="mojikumi-line-end">，</span>但正常的英文很少会遇到特别长的单词<span class="mojikumi-line-end">，</span>代码则经常遇到<span class="mojikumi-line-end">，</span>所以我选择对代码进行处理<span class="mojikumi-line-start">（</span>主要是因为不需要分词套 <code>&#x3C;span></code><span class="mojikumi">）</span><span class="mojikumi-line-end">。</span></p>
<p>思路很简单<span class="mojikumi-line-end">，</span>就是把过长的 <code>:not(pre) > code</code> 通过 <a href="https://developer.mozilla.org/docs/Web/HTML/Element/wbr"><code>&#x3C;wbr></code></a> 或者 <a href="https://developer.mozilla.org/docs/Web/CSS/word-break"><code>word<wbr>-<wbr>break<wbr>: <wbr>break<wbr>-<wbr>all</code></a> 断开<span class="mojikumi-line-end">，</span>让它不可断开的部分没那么长<span class="mojikumi-line-end">。</span>例如<span class="mojikumi-line-end">：</span><code>&#x3C;<wbr>code<wbr>><wbr>word<wbr>&#x3C;<wbr>wbr<wbr>>-&#x3C;<wbr>wbr<wbr>><wbr>break<wbr>&#x3C;<wbr>wbr<wbr>>: &#x3C;<wbr>wbr<wbr>><wbr>break<wbr>&#x3C;<wbr>wbr<wbr>>-&#x3C;<wbr>wbr<wbr>><wbr>all<wbr>&#x3C;/<wbr>code<wbr>></code><span class="mojikumi-line-end">、</span><code>&#x3C;<wbr>code<wbr>>&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>code<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>word<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;-&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>break<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;: &#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>break<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;-&#x26;<wbr>lt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>wbr<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;&#x3C;<wbr>wbr<wbr>><wbr>all<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>lt<wbr>;/&#x3C;<wbr>wbr<wbr>><wbr>code<wbr>&#x3C;<wbr>wbr<wbr>>&#x26;<wbr>gt<wbr>;&#x3C;/<wbr>code<wbr>></code>……<span class="mojikumi-line-start">（</span><s>禁止禁止套娃</s><span class="mojikumi">）</span><span class="mojikumi-line-end">。</span></p>
<p>具体实现<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/iles-blog/blob/master/src/rehype/breakLongCode.ts"><code>breakLongCode<wbr>.<wbr>ts</code></a></p>
<ol>
<li>如果按空格分开没有超过 10 个字符的部分<span class="mojikumi-line-end">，</span>不进行处理<span class="mojikumi-line-end">；</span></li>
<li>如果按正则表达式的 <code>\b</code> 分开没有超过 12 个字符的部分<span class="mojikumi-line-end">，</span>则在 <code>\b</code> 处插入 <code>&#x3C;wbr></code><span class="mojikumi-line-end">；</span></li>
<li>如果按 <code>\b</code> 以及下划线分开没有超过 15 个字符的部分<span class="mojikumi-line-end">，</span>则在相应位置插入 <code>&#x3C;wbr></code><span class="mojikumi-line-end">；</span></li>
<li>否则<span class="mojikumi-line-end">，</span>没救了<span class="mojikumi-line-end">，</span>直接 <code>word<wbr>-<wbr>break<wbr>: <wbr>break<wbr>-<wbr>all</code><span class="mojikumi-line-end">。</span></li>
</ol>
<p>当然这个粗糙的处理方式还是有一些问题<span class="mojikumi-line-end">，</span>比如从上面的示例就可以看出来<span class="mojikumi-line-end">，</span>期望结果可能是 <code>&#x3C;wbr></code> / <code>&#x3C;code></code> 不被断开<span class="mojikumi-line-end">，</span>但实际上会在 <code>&#x3C;</code> 后 / <code>></code> 前断开<span class="mojikumi-line-end">。</span></p>
<p>UPD<span class="mojikumi-line-end">：</span>链接也可以用同样的方法进行处理<span class="mojikumi-line-end">。</span></p>
<h3 id="在小屏幕上对窄的元素禁用-text-justify" class="heading"><a href="#在小屏幕上对窄的元素禁用-text-justify" class="heading-anchor" aria-label="章节： 在小屏幕上对窄的元素禁用 text justify" tabindex="-1"></a><span>在小屏幕上对窄的元素禁用 text justify</span></h3>
<p style="margin: auto; width: 7em; border: gray solid 1px;">有的时候容器宽度太小<span class="mojikumi-line-end">，</span>即使是正常的普通文本也不适合进行 text justify<span class="mojikumi-line-end">。</span></p>
<p>我选择了一个非常简单粗暴的处理方法<span class="mojikumi-line-end">：</span>认为窄的元素 = 小屏幕上嵌套了多级的元素<span class="mojikumi-line-end">。</span></p>
<section class="code-block relative my-6 shadow" itemprop="hasPart" itemscope itemtype="https://schema.org/SoftwareSourceCode" data-v-c675dba6><div class="h-6 items-center rd-t-1 bg-area px-4 dark:bg-#2A313A media-screen:important-flex" style="display:none;" data-v-c675dba6><h4 class="text-3 text-footer" itemprop="programmingLanguage" aria-label="CSS 代码块" data-v-c675dba6>CSS</h4><ile-root id="ile-1"><button title="复制到剪贴板" class="copy-button b-footer text-footer" data-v-63dfb2af><span class="i-mdi-content-copy" data-v-63dfb2af></span><span class="sr-only" role="status" data-v-63dfb2af></span></button></ile-root><!--ISLAND_HYDRATION_PLACEHOLDER_ile-1--></div><div class="dark:hidden" itemprop="text" data-v-c675dba6><pre class="shiki light" style="background-color: #FBFBFB" tabindex="0"><code><span><span style="color: #4876D6">.article-style</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">  </span><span style="color: #0C969B">text-align</span><span style="color: #403F53">: </span><span style="color: #C96765">justify</span><span style="color: #403F53">;</span></span>
<span><span style="color: #403F53">}</span></span>
<span><span style="color: #4876D6">.article-style</span><span style="color: #994CC3"> </span><span style="color: #0C969B">&gt;</span><span style="color: #994CC3"> </span><span style="color: #0C969B">*</span><span style="color: #994CC3"> </span><span style="color: #0C969B">&gt;</span><span style="color: #994CC3"> </span><span style="color: #0C969B">*</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">  </span><span style="color: #0C969B">text-align</span><span style="color: #403F53">: </span><span style="color: #C96765">initial</span><span style="color: #403F53">;</span></span>
<span><span style="color: #403F53">}</span></span>
<span><span style="color: #994CC3">@media</span><span style="color: #403F53"> </span><span style="color: #111111">(</span><span style="color: #0C969B">min-width</span><span style="color: #403F53">: </span><span style="color: #AA0982">40em</span><span style="color: #111111">)</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">  </span><span style="color: #4876D6">.article-style</span><span style="color: #994CC3"> </span><span style="color: #0C969B">&gt;</span><span style="color: #994CC3"> </span><span style="color: #0C969B">*</span><span style="color: #994CC3"> </span><span style="color: #0C969B">&gt;</span><span style="color: #994CC3"> </span><span style="color: #0C969B">*</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">    </span><span style="color: #0C969B">text-align</span><span style="color: #403F53">: </span><span style="color: #C96765">inherit</span><span style="color: #403F53">;</span></span>
<span><span style="color: #403F53">  }</span></span>
<span><span style="color: #403F53">}</span></span></code></pre></div><div class="dark:important-block" style="display:none;" data-v-c675dba6><pre class="shiki dark" style="background-color: #011627" tabindex="0"><code><span><span style="color: #C5E478">.article-style</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #80CBC4">text-align</span><span style="color: #D6DEEB">: </span><span style="color: #FF6363">justify</span><span style="color: #D6DEEB">;</span></span>
<span><span style="color: #D6DEEB">}</span></span>
<span><span style="color: #C5E478">.article-style</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">&gt;</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">*</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">&gt;</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">*</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #80CBC4">text-align</span><span style="color: #D6DEEB">: </span><span style="color: #FF6363">initial</span><span style="color: #D6DEEB">;</span></span>
<span><span style="color: #D6DEEB">}</span></span>
<span><span style="color: #C792EA">@media</span><span style="color: #D6DEEB"> </span><span style="color: #D9F5DD">(</span><span style="color: #80CBC4">min-width</span><span style="color: #D6DEEB">: </span><span style="color: #F78C6C">40</span><span style="color: #FFEB95">em</span><span style="color: #D9F5DD">)</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #C5E478">.article-style</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">&gt;</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">*</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">&gt;</span><span style="color: #C792EA"> </span><span style="color: #7FDBCA">*</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">    </span><span style="color: #80CBC4">text-align</span><span style="color: #D6DEEB">: </span><span style="color: #FF6363">inherit</span><span style="color: #D6DEEB">;</span></span>
<span><span style="color: #D6DEEB">  }</span></span>
<span><span style="color: #D6DEEB">}</span></span></code></pre></div></section>
<p><span class="mojikumi-line-start">（</span>上面放的是编译出来的 CSS<span class="mojikumi-line-end">，</span><a href="https://github.com/ouuan/iles-blog/blob/master/src/styles/article.scss">源码</a>是使用 UnoCSS 的 SCSS<span class="mojikumi">。</span><span class="mojikumi-line-end">）</span></p>
<p>一些细节<span class="mojikumi-line-end">：</span></p>
<ul>
<li>使用 <code>*</code> 选择器<span class="mojikumi-line-end">，</span><a href="https://developer.mozilla.org/docs/Web/CSS/Specificity">specificity</a> 很低<span class="mojikumi-line-end">，</span>便于 override<span class="mojikumi-line-end">。</span></li>
<li>只选择 <code>> * > *</code> 而非 <code> * *</code><span class="mojikumi-line-end">，</span>这样的话嵌套更深的元素依然从 parent 继承 <code>text-align</code><span class="mojikumi-line-end">，</span>就可以设置 CSS override 掉一整个子树<span class="mojikumi">。</span><wbr><span class="mojikumi-line-start">（</span>之前我一度以为 katex 不自带行间公式居中<span class="mojikumi-line-end">，</span>后来发现是被我的 CSS 改掉了 <code>text-align</code><span class="mojikumi">。</span><span class="mojikumi-line-end">）</span></li>
<li>使用 <code>inherit</code> 而非 <code>justify</code> 进行撤销<span class="mojikumi-line-end">。</span></li>
</ul>
<h3 id="对包含过长行内公式的元素禁用-text-justify" class="heading"><a href="#对包含过长行内公式的元素禁用-text-justify" class="heading-anchor" aria-label="章节： 对包含过长行内公式的元素禁用 text justify" tabindex="-1"></a><span>对包含过长行内公式的元素禁用 text justify</span></h3>
<p>除了 <code>行内代码</code><span class="mojikumi-line-end">，</span><span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>行内公式</mtext></mrow><annotation encoding="application/x-tex">\text{行内公式}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord text"><span class="mord cjk_fallback">行内公式</span></span></span></span></span></span> 也是常见的过长的不可断元素<span class="mojikumi-line-end">。</span>这里我采用了一个本文最 dirty 的处理方式<span class="mojikumi-line-end">：</span>在客户端通过 JavaScript 检测宽度过大的行内公式<span class="mojikumi-line-end">。</span></p>
<p>具体实现<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/iles-blog/blob/master/src/components/TextJustifyFix.vue"><code>TextJustifyFix<wbr>.<wbr>vue</code></a></p>
<ul>
<li>只修改 <code>text<wbr>-<wbr>align<wbr>: <wbr>justify</code> 的元素<span class="mojikumi-line-end">，</span>如果已经设置为其他对齐方式<span class="mojikumi-line-start">（</span>例如表格中的居中对齐<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>就不应修改<span class="mojikumi-line-end">。</span></li>
<li>使用了 <a href="https://developer.mozilla.org/docs/Web/API/FontFaceSet/ready"><code>document<wbr>.<wbr>fonts<wbr>.<wbr>ready</code></a> 来等待字体全部加载好再检查宽度<span class="mojikumi-line-end">。</span></li>
</ul>
<h2 id="标点挤压" class="heading"><a href="#标点挤压" class="heading-anchor" aria-label="章节： 标点挤压" tabindex="-1"></a><span>标点挤压</span></h2>
<p>代码实现<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/iles-blog/blob/master/src/remark/mojikumi.ts"><code>mojikumi<wbr>.<wbr>ts</code></a><span class="mojikumi-line-end">、</span><a href="https://github.com/ouuan/iles-blog/blob/master/src/styles/mojikumi.scss"><code>mojikumi<wbr>.<wbr>scss</code></a><span class="mojikumi-line-end">。</span></p>
<h3 id="相邻标点的挤压" class="heading"><a href="#相邻标点的挤压" class="heading-anchor" aria-label="章节： 相邻标点的挤压" tabindex="-1"></a><span>相邻标点的挤压</span></h3>
<p>例如<span class="mojikumi">：</span><wbr><span class="mojikumi-line-start">「</span><span class="no-mojikumi"><span class="mojikumi">：</span><wbr><span class="mojikumi-line-start">“</span><span class="mojikumi">。</span><span class="mojikumi">”</span><wbr><span class="mojikumi-line-start">（</span><span class="mojikumi">。</span><span class="mojikumi-line-end">）</span></span><span class="mojikumi-line-end">」</span>→<span class="mojikumi-line-start">「</span><span><span class="mojikumi">：</span><wbr><span class="mojikumi-line-start">“</span><span class="mojikumi">。</span><span class="mojikumi">”</span><wbr><span class="mojikumi-line-start">（</span><span class="mojikumi">。</span><span class="mojikumi-line-end">）</span></span><span class="mojikumi">」</span><span class="mojikumi-line-end">。</span>当然本页其他地方也有一些例子<span class="mojikumi-line-end">。</span></p>
<p>具体规则 <a href="https://www.w3.org/TR/2022/DNOTE-clreq-20221009/#compression_rules_for_consecutive_punctuation_marks">clreq</a> 中说的比较模糊<span class="mojikumi-line-end">，</span>可以参考 <a href="https://www.w3.org/TR/2020/NOTE-jlreq-20200811/#positioning_of_consecutive_opening_brackets_closing_brackets_comma_full_stops_and_middle_dots">jlreq</a><span class="mojikumi-line-end">。</span></p>
<h3 id="使用-css-去除标点的一半空白" class="heading"><a href="#使用-css-去除标点的一半空白" class="heading-anchor" aria-label="章节： 使用 CSS 去除标点的一半空白" tabindex="-1"></a><span>使用 CSS 去除标点的一半空白</span></h3>
<p>CSS 实现标点挤压有两种方式<span class="mojikumi-line-end">：</span></p>
<ul>
<li>使用 <a href="https://developer.mozilla.org/docs/Web/CSS/letter-spacing"><code>letter<wbr>-<wbr>spacing<wbr>: -<wbr>0<wbr>.<wbr>5em</code></a> 去掉右侧空白<span class="mojikumi-line-end">，</span><code>margin<wbr>-<wbr>left<wbr>: -<wbr>0<wbr>.<wbr>5em</code> 去掉左侧空白<span class="mojikumi-line-end">。</span></li>
<li>使用 <a href="https://developer.mozilla.org/docs/Web/CSS/font-feature-settings"><code>font<wbr>-<wbr>feature<wbr>-<wbr>settings<wbr>:</code></a> <a href="https://learn.microsoft.com/typography/opentype/spec/features_fj#halt"><code>"halt"</code></a> 将支持 halt 特性的字体变为半宽<span class="mojikumi-line-end">。</span></li>
</ul>
<p>使用 <code>letter<wbr>-<wbr>spacing</code> 是有讲究的<span class="mojikumi-line-end">，</span>比 <code>margin<wbr>-<wbr>right</code> 靠谱<span class="mojikumi-line-end">，</span>因为字符真的会只占半宽<span class="mojikumi-line-end">，</span>而不是占全宽但一半和下一个字符重叠<span class="mojikumi-line-end">，</span>甚至造成 overflow<span class="mojikumi-line-end">，</span>可能导致滚动条出现<span class="mojikumi-line-end">。</span>但 <code>letter<wbr>-<wbr>spacing</code> 不能去掉左侧空白<span class="mojikumi-line-end">，</span>就只能使用 <code>margin<wbr>-<wbr>left</code> 了<span class="mojikumi-line-end">。</span></p>
<p>通过 halt 特性变为半宽是更加靠谱的做法<span class="mojikumi-line-end">，</span>但不是所有字体都支持这一特性<span class="mojikumi-line-end">，</span>思源宋体是支持的<span class="mojikumi-line-end">，</span><span class="font-kai">霞鹜文楷</span> 不支持<span class="mojikumi-line-end">。</span>并且<span class="mojikumi-line-end">，</span>halt 只能变为半宽<span class="mojikumi-line-end">，</span>不能变成其他宽度<span class="mojikumi-line-end">。</span></p>
<h3 id="行首行尾标点的挤压" class="heading"><a href="#行首行尾标点的挤压" class="heading-anchor" aria-label="章节： 行首行尾标点的挤压" tabindex="-1"></a><span>行首行尾标点的挤压</span></h3>
<p>这里参考了 <a href="https://github.com/ethantw/Han">Han.css</a> 的实现<span class="mojikumi-line-end">，</span>就是把全宽的标点改成半宽标点和一个通过 <code>::before</code> 或 <code>::end</code> 添加的半宽的空格<span class="mojikumi-line-end">：</span></p>
<ul>
<li>因为是 pseudo element<span class="mojikumi-line-end">，</span>所以不影响文本内容<span class="mojikumi-line-start">（</span>例如复制出来的文本<span class="mojikumi">）</span><span class="mojikumi-line-end">。</span></li>
<li>因为是空格<span class="mojikumi-line-end">，</span>所以位于行首或行尾时就会发生<a href="https://www.w3.org/TR/2023/CRD-css-text-3-20230127/#white-space-phase-2">空格塌陷</a><span class="mojikumi-line-end">。</span></li>
</ul>
<h3 id="英文标点的处理" class="heading"><a href="#英文标点的处理" class="heading-anchor" aria-label="章节： 英文标点的处理" tabindex="-1"></a><span>英文标点的处理</span></h3>
<p>我采用了一个比较简单的判断英文标点的方法<span class="mojikumi-line-end">：</span></p>
<ul>
<li>空白一侧<span class="mojikumi-line-start">（</span>左括号<span class="mojikumi-line-end">、</span>左引号等是左侧<span class="mojikumi-line-end">，</span>其他一般是右侧<span class="mojikumi-line-end">）</span>是空格时是英文标点<span class="mojikumi-line-end">；</span></li>
<li>右单引号右侧是字母时是撇号<span class="mojikumi-line-end">。</span></li>
</ul>
<p>英文标点一般调为半宽即可<span class="mojikumi-line-end">，</span>但撇号半宽还是有些太宽了<span class="mojikumi-line-end">，</span>应该<span class="mojikumi-line-start">（</span>根据字体而定<span class="mojikumi-line-end">）</span>调成大约三分之一宽<span class="mojikumi-line-end">，</span>单引号也可以调窄一点<span class="mojikumi-line-end">，</span>所以可以特判一下单引号<span class="mojikumi-line-end">，</span>使用 <code>letter<wbr>-<wbr>spacing</code> 和 <code>margin<wbr>-<wbr>left</code><span class="mojikumi-line-end">。</span></p>
<h3 id="给霞鹜文楷添加-halt-特性" class="heading"><a href="#给霞鹜文楷添加-halt-特性" class="heading-anchor" aria-label="章节： 给霞鹜文楷添加 halt 特性" tabindex="-1"></a><span>给霞鹜文楷添加 halt 特性</span></h3>
<p>因为 <a href="https://github.com/lxgw/LxgwWenKai/issues/104">添加 halt 特性的 issue</a> 被拒了<span class="mojikumi-line-end">，</span>我就自己改了一个 <a href="https://github.com/ouuan/XiaWu-Punctuations">标点字体</a><span class="mojikumi-line-end">，</span>顺便把引号改成了和思源宋体一致的全宽以方便一起处理<span class="mojikumi-line-start">（</span>也可以理解为改引号宽度顺便添加 halt 特性<span class="mojikumi-line-end">，</span>毕竟没有 halt 也可以用 <code>letter<wbr>-<wbr>spacing</code> 和 <code>margin<wbr>-<wbr>left</code> 凑合一下<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>具体可以看仓库里的 patch 文件<span class="mojikumi-line-end">。</span></p>
<h3 id="关于-chws-特性" class="heading"><a href="#关于-chws-特性" class="heading-anchor" aria-label="章节： 关于 chws 特性" tabindex="-1"></a><span>关于 chws 特性</span></h3>
<p><a href="https://learn.microsoft.com/typography/opentype/spec/features_ae#chws">chws 特性</a> 可以根据上下文自动挤压连续标点<span class="mojikumi-line-end">，</span>一眼看上去似乎比 halt 更好用<span class="mojikumi-line-end">。</span></p>
<p>思源宋体不支持这一特性<span class="mojikumi-line-end">，</span>只不过有 <a href="https://github.com/googlefonts/chws_tool">chws_tool</a> 可以用来转换<span class="mojikumi-line-end">。</span></p>
<p>但是<span class="mojikumi-line-end">，</span>chws 不支持行首行尾标点挤压<span class="mojikumi-line-end">，</span>而如果要支持行首行尾挤压<span class="mojikumi-line-end">，</span>就得把支持连续标点挤压的工作做一遍<span class="mojikumi-line-start">（</span>因为要知道哪些标点已经被挤成了半宽哪些只在行首行尾挤成半宽<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>这样一来 chws 就没多大意义了<span class="mojikumi-line-end">。</span></p>
<h3 id="yet-another-mojikumi？" class="heading"><a href="#yet-another-mojikumi？" class="heading-anchor" aria-label="章节： Yet another mojikumi？" tabindex="-1"></a><span>Yet another mojikumi？</span></h3>
<p>有一些现成的支持标点挤压的库<span class="mojikumi-line-end">，</span>例如 <a href="https://github.com/sivan/heti">heti</a> 和 <a href="https://github.com/ethantw/Han">Han.css</a><span class="mojikumi-line-end">。</span>但是我有一些需求<span class="mojikumi-line-end">：</span></p>
<ul>
<li>要能 SSG <span class="mojikumi">（</span>SSR<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>不能在客户端运行<span class="mojikumi-line-end">。</span>最好是能在 remark / rehype 中进行处理<span class="mojikumi-line-end">。</span></li>
<li>最好是尽量使用 halt 而非 <code>letter<wbr>-<wbr>spacing</code> 和 <code>margin<wbr>-<wbr>left</code><span class="mojikumi-line-end">。</span></li>
<li>要能添加一些自定义的规则<span class="mojikumi-line-end">，</span>比如对单引号特殊处理<span class="mojikumi-line-end">。</span></li>
<li>我只需要标点挤压<span class="mojikumi-line-end">，</span>不想要一个 CSS 全家桶<span class="mojikumi-line-end">，</span>或者带有其他功能的 JS<span class="mojikumi-line-end">。</span></li>
</ul>
<p>现有的库难以同时满足上面这些需求<span class="mojikumi-line-end">，</span>所以我就自己写了<span class="mojikumi-line-end">。</span></p>
<p>本来想写一个通用的库<span class="mojikumi-line-end">，</span>但通用的逻辑貌似没多少代码<span class="mojikumi-line-end">，</span>不值得写成一个库<span class="mojikumi-line-end">。</span>而我的整个 remark 插件又有点 opinionated<span class="mojikumi-line-end">，</span>可能不太适合做成库<span class="mojikumi-line-start">（</span><s>懒得做成库</s><span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>想用的话可以在遵守 <a href="https://github.com/ouuan/iles-blog/blob/master/LICENSE">AGPL 3.0</a> 的前提下直接复制<span class="mojikumi-line-start">（</span>本文提到的其他代码也是一样<span class="mojikumi-line-end">，</span>当然<span class="mojikumi-line-end">，</span>如果是两三行的代码片段就不至于 AGPL 了<span class="mojikumi-line-end">，</span>简单标一下出处就 OK<span class="mojikumi">）</span><span class="mojikumi-line-end">。</span></p>
<h2 id="代码中的-unicode（cjk）的字体" class="heading"><a href="#代码中的-unicode（cjk）的字体" class="heading-anchor" aria-label="章节： 代码中的 Unicode（CJK）的字体" tabindex="-1"></a><span>代码中的 Unicode<span class="mojikumi-line-start">（</span>CJK<span class="mojikumi-line-end">）</span>的字体</span></h2>
<p><s>为什么马上要考抽代了你在这更新博客<span class="mojikumi-line-end">，</span>我也很想知道<span class="mojikumi-line-start">（</span></s> 其实是在一个群里看到有人在聊博客的等宽字体<span class="mojikumi-line-end">，</span>然后看了一眼自己的等宽字体<span class="mojikumi-line-end">，</span>差点没想起来这个 trick 在干什么<span class="mojikumi-line-end">。</span></p>
<p>很多等宽字体是没有 CJK 字形的<span class="mojikumi-line-end">，</span>在 Windows 的默认字体下<span class="mojikumi-line-end">，</span>fallback 到 <code>monospace</code> 时中文会显示为<code style="font-family: NSimSun, SimSun, serif;">宋体</code><span class="mojikumi-line-end">，</span>而一般来说应当是<code>黑体</code><span class="mojikumi-line-end">。</span>所以可以为 Unicode 部分专门指定一个 fallback font<span class="mojikumi-line-end">，</span>通过指定 <code>unicode<wbr>-<wbr>range</code> 来让 ASCII 字符依然 fallback 到 <code>monospace</code><span class="mojikumi-line-end">。</span></p>
<section class="code-block relative my-6 shadow" itemprop="hasPart" itemscope itemtype="https://schema.org/SoftwareSourceCode" data-v-c675dba6><div class="h-6 items-center rd-t-1 bg-area px-4 dark:bg-#2A313A media-screen:important-flex" style="display:none;" data-v-c675dba6><h3 class="text-3 text-footer" itemprop="programmingLanguage" aria-label="CSS 代码块" data-v-c675dba6>CSS</h3><ile-root id="ile-2"><button title="复制到剪贴板" class="copy-button b-footer text-footer" data-v-63dfb2af><span class="i-mdi-content-copy" data-v-63dfb2af></span><span class="sr-only" role="status" data-v-63dfb2af></span></button></ile-root><!--ISLAND_HYDRATION_PLACEHOLDER_ile-2--></div><div class="dark:hidden" itemprop="text" data-v-c675dba6><pre class="shiki light" style="background-color: #FBFBFB" tabindex="0"><code><span><span style="color: #994CC3">@font-face</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">  </span><span style="color: #0C969B">font-family</span><span style="color: #403F53">: </span><span style="color: #111111">&quot;</span><span style="color: #C96765">Monospace Unicode Fallback</span><span style="color: #111111">&quot;</span><span style="color: #403F53">;</span></span>
<span><span style="color: #403F53">  </span><span style="color: #0C969B">src</span><span style="color: #403F53">: </span><span style="color: #4876D6">local</span><span style="color: #403F53">(</span><span style="color: #111111">&quot;</span><span style="color: #C96765">Noto Sans Mono CJK SC</span><span style="color: #111111">&quot;</span><span style="color: #403F53">),</span></span>
<span><span style="color: #403F53">       </span><span style="color: #4876D6">local</span><span style="color: #403F53">(</span><span style="color: #111111">&quot;</span><span style="color: #C96765">Source Han Mono SC</span><span style="color: #111111">&quot;</span><span style="color: #403F53">),</span></span>
<span><span style="color: #403F53">       </span><span style="color: #4876D6">local</span><span style="color: #403F53">(</span><span style="color: #111111">&quot;</span><span style="color: #C96765">Noto Sans CJK SC</span><span style="color: #111111">&quot;</span><span style="color: #403F53">),</span></span>
<span><span style="color: #403F53">       </span><span style="color: #4876D6">local</span><span style="color: #403F53">(</span><span style="color: #111111">&quot;</span><span style="color: #C96765">Source Han Sans SC</span><span style="color: #111111">&quot;</span><span style="color: #403F53">),</span></span>
<span><span style="color: #403F53">       </span><span style="color: #4876D6">local</span><span style="color: #403F53">(</span><span style="color: #111111">&quot;</span><span style="color: #C96765">Microsoft YaHei</span><span style="color: #111111">&quot;</span><span style="color: #403F53">);</span></span>
<span><span style="color: #403F53">  </span><span style="color: #0C969B">unicode-range</span><span style="color: #403F53">: </span><span style="color: #4876D6">U+1000-fffff</span><span style="color: #403F53">;</span></span>
<span><span style="color: #403F53">}</span></span>
<span></span>
<span><span style="color: #4876D6">:root</span><span style="color: #403F53"> {</span></span>
<span><span style="color: #403F53">  </span><span style="color: #4876D6">--default-mono-font</span><span style="color: #403F53">: </span><span style="color: #0C969B">ui-monospace</span><span style="color: #403F53">, DejaVu Sans Mono, Noto Sans Mono, SFMono-Regular, Menlo, Monaco, Consolas, </span><span style="color: #111111">&quot;</span><span style="color: #C96765">Monospace Unicode Fallback</span><span style="color: #111111">&quot;</span><span style="color: #403F53">, </span><span style="color: #0C969B">monospace</span><span style="color: #403F53">, </span><span style="color: #4876D6">var</span><span style="color: #403F53">(</span><span style="color: #4876D6">--default-emoji-font</span><span style="color: #403F53">);</span></span>
<span><span style="color: #403F53">}</span></span></code></pre></div><div class="dark:important-block" style="display:none;" data-v-c675dba6><pre class="shiki dark" style="background-color: #011627" tabindex="0"><code><span><span style="color: #C792EA">@font-face</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #80CBC4">font-family</span><span style="color: #D6DEEB">: </span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Monospace Unicode Fallback</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">;</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #80CBC4">src</span><span style="color: #D6DEEB">: </span><span style="color: #C5E478">local</span><span style="color: #D6DEEB">(</span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Noto Sans Mono CJK SC</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">),</span></span>
<span><span style="color: #D6DEEB">       </span><span style="color: #C5E478">local</span><span style="color: #D6DEEB">(</span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Source Han Mono SC</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">),</span></span>
<span><span style="color: #D6DEEB">       </span><span style="color: #C5E478">local</span><span style="color: #D6DEEB">(</span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Noto Sans CJK SC</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">),</span></span>
<span><span style="color: #D6DEEB">       </span><span style="color: #C5E478">local</span><span style="color: #D6DEEB">(</span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Source Han Sans SC</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">),</span></span>
<span><span style="color: #D6DEEB">       </span><span style="color: #C5E478">local</span><span style="color: #D6DEEB">(</span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Microsoft YaHei</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">);</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #80CBC4">unicode-range</span><span style="color: #D6DEEB">: </span><span style="color: #82AAFF">U+1000-fffff</span><span style="color: #D6DEEB">;</span></span>
<span><span style="color: #D6DEEB">}</span></span>
<span></span>
<span><span style="color: #C5E478">:root</span><span style="color: #D6DEEB"> {</span></span>
<span><span style="color: #D6DEEB">  </span><span style="color: #C5E478">--default-mono-font</span><span style="color: #D6DEEB">: </span><span style="color: #7FDBCA">ui-monospace</span><span style="color: #D6DEEB">, DejaVu Sans Mono, Noto Sans Mono, SFMono-Regular, Menlo, Monaco, Consolas, </span><span style="color: #D9F5DD">&quot;</span><span style="color: #ECC48D">Monospace Unicode Fallback</span><span style="color: #D9F5DD">&quot;</span><span style="color: #D6DEEB">, </span><span style="color: #7FDBCA">monospace</span><span style="color: #D6DEEB">, </span><span style="color: #C5E478">var</span><span style="color: #D6DEEB">(</span><span style="color: #C5E478">--default-emoji-font</span><span style="color: #D6DEEB">);</span></span>
<span><span style="color: #D6DEEB">}</span></span></code></pre></div></section>
<h2 id="为不同语言设置不同字体" class="heading"><a href="#为不同语言设置不同字体" class="heading-anchor" aria-label="章节： 为不同语言设置不同字体" tabindex="-1"></a><span>为不同语言设置不同字体</span></h2>
<p>其实我感觉我的做法有点丑<span class="mojikumi-line-start">（</span><span class="mojikumi-line-end">，</span>还好有 UnoCSS 稍微强一点<span class="mojikumi-line-start">（</span></p>
<ul>
<li>为每个样式<span class="mojikumi-line-start">（</span>例如宋/<span class="font-kai">楷</span><span class="mojikumi-line-end">）</span>和每个语言分别用一个 CSS 变量记录当前字体<span class="mojikumi-line-end">。</span></li>
<li>改变样式时修改 <code>font<wbr>-<wbr>family</code> 为这个样式的字体<span class="mojikumi-line-end">，</span>修改每个语言的字体为这个样式<span class="mojikumi-line-end">。</span></li>
<li>改变语言时修改 <code>font<wbr>-<wbr>family</code> 为这个语言的字体<span class="mojikumi-line-end">，</span>修改每个样式的字体为这个语言<span class="mojikumi-line-end">。</span></li>
</ul>
<p>语言通过 <code>lang</code> 属性标识<span class="mojikumi-line-end">。</span></p>
<p>详见 <a href="https://github.com/ouuan/iles-blog/commit/e8df882f4a550bfd5db50f837f0a9ab376262dc4">commit</a><span class="mojikumi-line-end">。</span></p>]]></content:encoded>
            <category domain="https://ouuan.moe/tag/blog">blog</category>
            <category domain="https://ouuan.moe/tag/CSS">CSS</category>
            <category domain="https://ouuan.moe/tag/Web">Web</category>
            <category domain="https://ouuan.moe/tag/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB">经验分享</category>
        </item>
        <item>
            <title><![CDATA[另一种很新的中文字体网页嵌入方案]]></title>
            <link>https://ouuan.moe/post/2023/01/glyph-segregator</link>
            <guid>https://ouuan.moe/post/2023/01/glyph-segregator</guid>
            <pubDate>Thu, 19 Jan 2023 13:15:05 GMT</pubDate>
            <description><![CDATA[<p>当你看到 <a href="https://ayu.land/webfont">一种很新的中文字体网页嵌入方案</a><span class="mojikumi-line-end">，</span>但是懒得优化 DP<span class="mojikumi-line-end">，</span>也不会进化算法<span class="mojikumi-line-end">，</span>你可以……</p>
<p>整一个<a href="https://github.com/ouuan/glyph-segregator"><em>另</em>一种很新的中文字体网页嵌入方案</a><span class="mojikumi-line-start">（</span></p>
]]></description>
            <content:encoded><![CDATA[<p>当你看到 <a href="https://ayu.land/webfont">一种很新的中文字体网页嵌入方案</a><span class="mojikumi-line-end">，</span>但是懒得优化 DP<span class="mojikumi-line-end">，</span>也不会进化算法<span class="mojikumi-line-end">，</span>你可以……</p>
<p>整一个<a href="https://github.com/ouuan/glyph-segregator"><em>另</em>一种很新的中文字体网页嵌入方案</a><span class="mojikumi-line-start">（</span></p>

<p><s>这就是你 <a href="/post/2023/01/ddpp">开坑 DDPP</a> 不填的理由吗</s></p>
<h2 id="背景" class="heading"><a href="#背景" class="heading-anchor" aria-label="章节： 背景" tabindex="-1"></a><span>背景</span></h2>
<p>前不久看到 <a href="https://ayu.land/webfont">一种很新的中文字体网页嵌入方案</a><span class="mojikumi-line-end">，</span>还想着没必要做这种优化<span class="mojikumi-line-end">，</span>毕竟按 Google Fonts 进行子集化就可以做到每页 1MB 左右<span class="mojikumi-line-end">，</span>按现代的网速不会有太大问题<span class="mojikumi-line-end">，</span>用 devtools 开节流试了下看起来也还行<span class="mojikumi-line-end">。</span></p>
<p>然后我回家了<span class="mojikumi-line-end">，</span>离开了校园网<span class="mojikumi-line-end">。</span>虽然自定义域名的 Cloudflare Pages 还能访问<span class="mojikumi-line-end">，</span>但速度暂且不论<span class="mojikumi-line-end">，</span>丢包率就很有点恐怖<span class="mojikumi-line-end">，</span>经常页面加载一半就卡住了<span class="mojikumi-line-end">。</span></p>
<p>再然后<span class="mojikumi-line-end">，</span>Google Fonts 里 Noto Serif SC 的 unicode range 不含单引号<span class="mojikumi-line-start">（</span>和撇号是同一个字符<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>导致 <a href="/post/2023/01/ddpp">DDPP 序</a> 里引用的那段话的撇号在我的手机上显示成了很宽的另一种字体<span class="mojikumi-line-end">，</span>才让我<a href="https://github.com/ouuan/iles-blog/commit/080bd7112f88d2eae1e86594f8820c0c517e37ce">发现这个问题</a><span class="mojikumi-line-end">。</span>为了以后能及时发现这种问题<span class="mojikumi-line-end">，</span>我就设置了不使用本地字体直接使用 web font 的 Stylus<span class="mojikumi-line-end">。</span></p>
<p>恐怖的丢包率<span class="mojikumi-line-end">，</span>加上自己亲身体验 web font 加载<span class="mojikumi-line-end">，</span>让我意识到了<span class="mojikumi-line-end">，</span>在国内普通网络环境下<span class="mojikumi-line-end">，</span>如果你的设备没有安装思源宋体<span class="mojikumi-line-end">，</span>访问我的博客会是多么难受<span class="mojikumi-line-start">（</span></p>
<h2 id="主体思路" class="heading"><a href="#主体思路" class="heading-anchor" aria-label="章节： 主体思路" tabindex="-1"></a><span>主体思路</span></h2>
<p><span class="mojikumi-line-start">“</span>另一种<span class="mojikumi-line-end">”</span>的主体思路<a href="https://ayu.land/webfont#principle">和<span class="mojikumi-line-start">“</span>一种<span class="mojikumi-line-end">”</span>是一样的</a><span class="mojikumi-line-end">，</span>就是把字体划分成常用字和非常用字<span class="mojikumi-line-end">。</span>但是<span class="mojikumi-line-start">“</span>一种<span class="mojikumi-line-end">”</span>在这之后选择了使用动态规划 + 进化算法来对常用字进行进一步的拆分<span class="mojikumi">，</span><wbr><span class="mojikumi-line-start">“</span>另一种<span class="mojikumi-line-end">”</span>所做的优化则基于这样一个观察<span class="mojikumi-line-end">：</span>虽然一个博客有一堆页面<span class="mojikumi-line-end">，</span>但 80% 的人只会访问 20% 的页面<span class="mojikumi-line-start">（</span><span class="mojikumi-line-end">，</span>一个字体是否常用<span class="mojikumi-line-end">，</span>不仅要看出现在几个页面<span class="mojikumi-line-end">，</span>还要看出现在哪些页面<span class="mojikumi-line-end">。</span></p>
<p>这时候<span class="mojikumi-line-end">，</span><s>一直充当<a href="https://busuanzi.ibruce.info/">不蒜子</a>平替<span class="mojikumi-line-start">（</span>哪平价了啊<span class="mojikumi-line-end">）</span>的</s> Plausible Analytics 就发挥作用了<span class="mojikumi-line-end">：</span>近段时间的页面访问量可以用来估计每个页面被访问的概率<span class="mojikumi-line-end">，</span>为常用字的划分提供可靠的数据支撑<span class="mojikumi-line-end">。</span></p>
<p>一旦估计出了每个页面的被访问概率<span class="mojikumi-line-end">，</span>就可以对每个 glyph 分别计算出<span class="mojikumi-line-end">，</span>如果在每一个使用了它的页面上都加载一遍<span class="mojikumi-line-end">，</span>期望代价是多少<span class="mojikumi-line-end">，</span>也就是使用了它的所有页面的被访问概率之和<span class="mojikumi-line-end">。</span>如果这个数大于 1<span class="mojikumi-line-end">，</span>就设为常用<span class="mojikumi-line-end">，</span>否则设为不常用<span class="mojikumi-line-end">。</span></p>
<p>这个思路还是非常简单的<span class="mojikumi-line-end">，</span>可以说只是<span class="mojikumi-line-start">“</span>结合实际<span class="mojikumi-line-end">，</span>采取启发式方法<span class="mojikumi">”</span><span class="mojikumi-line-end">，</span>也称不上是一种新的方案<span class="mojikumi-line-start">（</span></p>
<h2 id="具体实现" class="heading"><a href="#具体实现" class="heading-anchor" aria-label="章节： 具体实现" tabindex="-1"></a><span>具体实现</span></h2>
<p>虽然思路简单<span class="mojikumi-line-end">，</span>但实现起来还是有点复杂的<span class="mojikumi-line-end">，</span>坑有点多<span class="mojikumi-line-end">。</span></p>
<h3 id="获取页面使用的字体" class="heading"><a href="#获取页面使用的字体" class="heading-anchor" aria-label="章节： 获取页面使用的字体" tabindex="-1"></a><span>获取页面使用的字体</span></h3>
<p>如果整个页面都使用一种字体<span class="mojikumi-line-end">，</span>直接看 HTML 里有哪些中文就差不多了<span class="mojikumi-line-end">，</span>但我不仅<span class="font-sans">非正文用的是黑体</span><span class="mojikumi-line-end">，</span><span class="font-kai">引用块还是楷体</span><span class="mojikumi-line-end">，</span>说不定在哪冒出来个<code>等宽</code>或者 <span class="math math-inline"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LaTeX公式</mtext></mrow><annotation encoding="application/x-tex">\LaTeX\text{公式}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8988em;vertical-align:-0.2155em;"></span><span class="mord text"><span class="mord textrm">L</span><span class="mspace" style="margin-right:-0.36em;"></span><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6833em;"><span style="top:-2.905em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord"><span class="mord textrm mtight sizing reset-size6 size3">A</span></span></span></span></span></span><span class="mspace" style="margin-right:-0.15em;"></span><span class="mord text"><span class="mord textrm">T</span><span class="mspace" style="margin-right:-0.1667em;"></span><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.4678em;"><span style="top:-2.7845em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord textrm">E</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2155em;"><span></span></span></span></span><span class="mspace" style="margin-right:-0.125em;"></span><span class="mord textrm">X</span></span></span><span class="mord text"><span class="mord cjk_fallback">公式</span></span></span></span></span></span><span class="mojikumi-line-end">，</span>有时候还会<strong>加粗</strong><span class="mojikumi-line-end">，</span>更别提心情好的时候会像这段一样直接塞 <code>&#x3C;span class=""></code> 来修改样式<span class="mojikumi-line-end">。</span></p>
<p>一开始我通过 <a href="https://github.com/Munter/subfont">subfont</a> 找到了 <a href="https://github.com/assetgraph/font-tracer">assetgraph/font-tracer</a><span class="mojikumi-line-end">，</span>但这个 font-tracer 就两个 star<span class="mojikumi-line-end">，</span>没有文档<span class="mojikumi-line-end">，</span><a href="https://github.com/assetgraph/assetgraph">AssetGraph</a> 又是一个 12 年前的老项目<span class="mojikumi-line-end">，</span>连 Definitely Typed 都没有<span class="mojikumi-line-end">，</span>试了一下完全用不会<span class="mojikumi-line-end">。</span></p>
<p>想了想<span class="mojikumi-line-end">，</span>最靠谱的还是直接交给浏览器来处理<span class="mojikumi-line-end">，</span>所以就用 <a href="https://github.com/puppeteer/puppeteer">puppeteer</a> 了<span class="mojikumi-line-end">。</span>目前采用的算法是<span class="mojikumi-line-end">：</span>遍历 <code>&#x3C;body></code><span class="mojikumi-line-end">，</span>跳过 <code>&#x3C;script></code> 和 <code>&#x3C;style></code> 子树<span class="mojikumi-line-end">，</span>找到所有 <a href="https://developer.mozilla.org/docs/Web/API/Text">Text 节点</a><span class="mojikumi-line-end">，</span>再加上所有 <code>&#x3C;img></code> 的 <code>alt</code> 属性<span class="mojikumi-line-end">。</span>找到节点后<span class="mojikumi-line-end">，</span>可以用 <a href="https://developer.mozilla.org/docs/Web/API/Window/getComputedStyle"><code class="break-all">getComputedStyle</code></a> 获取 <code>font<wbr>-<wbr>family</code> 和 <code>font<wbr>-<wbr>weight</code> 等信息<span class="mojikumi-line-end">。</span></p>
<p>因为要处理很多页面<span class="mojikumi-line-end">，</span>也要花一点时间<span class="mojikumi-line-end">，</span>所以用了 <a href="https://github.com/thomasdondorf/puppeteer-cluster">puppeteer-cluster</a> 来并行处理<span class="mojikumi">。</span><wbr><span class="mojikumi-line-start">（</span>不知道为什么<span class="mojikumi-line-end">，</span>在 vitest 和 iles 中运行时<span class="mojikumi-line-end">，</span>不加参数会报错<span class="mojikumi-line-end">，</span>而加了 <code>--<wbr>no<wbr>-<wbr>sandbox<wbr> --<wbr>no<wbr>-<wbr>zygote</code> 之后并行的优化效果就差一些<span class="mojikumi">。</span><span class="mojikumi-line-end">）</span></p>
<p>代码<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/glyph-segregator/blob/master/src/getPagesFontInfo.ts"><code class="break-all">getPagesFontInfo.ts</code></a></p>
<h3 id="字体匹配" class="heading"><a href="#字体匹配" class="heading-anchor" aria-label="章节： 字体匹配" tabindex="-1"></a><span>字体匹配</span></h3>
<p><a href="https://www.w3.org/TR/css-fonts-4/#font-style-matching">完整的字体匹配</a>是非常复杂的<span class="mojikumi-line-end">，</span>我也没找到相关的库<span class="mojikumi-line-end">，</span>就自己写了一个简陋的只匹配完整 <code>font<wbr>-<wbr>family</code> 和 <code>font<wbr>-<wbr>weight</code> 的算法<span class="mojikumi-line-end">。</span></p>
<p>其实一般 <code>font<wbr>-<wbr>weight</code> 也就 400 和 700<span class="mojikumi-line-end">，</span>但我还是实现了一个 <a href="https://developer.mozilla.org/docs/Web/CSS/font-weight#fallback_weights">完整的 <code>font<wbr>-<wbr>weight</code> fallback</a><span class="mojikumi-line-end">。</span></p>
<p>代码<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/glyph-segregator/blob/master/src/matchFonts.ts"><code>matchFonts<wbr>.<wbr>ts</code></a></p>
<h3 id="常用字体划分" class="heading"><a href="#常用字体划分" class="heading-anchor" aria-label="章节： 常用字体划分" tabindex="-1"></a><span>常用字体划分</span></h3>
<p>最关键的这一步实现起来是最简单的<span class="mojikumi-line-end">，</span>因为是纯算法的<span class="mojikumi-line-end">，</span>不涉及到可怕的 Web<span class="mojikumi-line-start">（</span></p>
<p>代码<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/glyph-segregator/blob/master/src/getCommonGlyphs.ts"><code>getCommonGlyphs<wbr>.<wbr>ts</code></a></p>
<h3 id="生成字体文件和-font-face" class="heading"><a href="#生成字体文件和-font-face" class="heading-anchor" aria-label="章节： 生成字体文件和 @font-face" tabindex="-1"></a><span>生成字体文件和 @font-face</span></h3>
<p>我使用的是 <a href="https://github.com/papandreou/subset-font">subset-font</a> 来生成字体子集文件<span class="mojikumi-line-end">。</span></p>
<p>这一步最大的困难<span class="mojikumi-line-end">，</span>是正确地写出 <code>@font-face</code><span class="mojikumi-line-end">。</span></p>
<p>一开始我是给常用字 (common) 和非常用字 (unique) 不同的 <code>font<wbr>-<wbr>family</code><span class="mojikumi-line-end">，</span>然后发现<span class="mojikumi-line-end">，</span>在特殊情况下<span class="mojikumi-line-start">（</span>幸好我的博客文章列表就触发了这个问题<span class="mojikumi-line-end">，</span>不然真没想到<span class="mojikumi">）</span><span class="mojikumi-line-end">，</span>某个字的常规样式属于 common 而粗体属于 unique<span class="mojikumi-line-end">，</span>由于 <code>font<wbr>-<wbr>family</code> 里 common 排在 unique 前面<span class="mojikumi-line-end">，</span>就会匹配到常规样式<span class="mojikumi-line-end">，</span>而加载不出粗体<span class="mojikumi-line-end">。</span></p>
<p>为了解决这个问题<span class="mojikumi-line-end">，</span>common 和 unique 需要使用同一个 <code>font<wbr>-<wbr>family</code><span class="mojikumi-line-end">。</span>因为用过 Google Fonts 子集划分方案<span class="mojikumi-line-end">，</span>我知道有不同 <code>unicode<wbr>-<wbr>range</code> 的 <code>@font-face</code> 是可以组合在一起的<span class="mojikumi-line-end">，</span>难道我需要计算出精确的 <code>unicode<wbr>-<wbr>range</code> 写在 CSS 里？</p>
<p>为此<span class="mojikumi-line-end">，</span>我粗略地读了半天<span class="mojikumi-line-start">（</span><s>这两个词不冲突</s><span class="mojikumi-line-end">）</span><a href="https://www.w3.org/TR/css-fonts-4/">CSS Fonts Module Level 4 W3C Working Draft</a><span class="mojikumi-line-end">，</span>发现它说<span class="mojikumi-line-end">，</span><code>unicode<wbr>-<wbr>range</code> 可以比真实的 character map 大很多<span class="mojikumi-line-end">，</span>若干 <code>@font-face</code> 的 <code>unicode<wbr>-<wbr>range</code> 也可以相交<span class="mojikumi-line-end">。</span>但是<span class="mojikumi-line-end">，</span>它没说两个 <code>@font-face</code> 能不能除了 <code>src</code> 啥都一样<span class="mojikumi-line-end">，</span><a href="https://www.w3.org/TR/css-fonts-4/#composite-fonts">而只说了</a><span class="mojikumi-line-end">：</span></p>
<blockquote>
<p>Multiple @font-face rules with different unicode ranges for the same family and style descriptor values can be used to create composite fonts that mix the glyphs from different fonts for different scripts.</p>
</blockquote>
<p>我也不敢依赖于实验结果<span class="mojikumi-line-end">，</span>就没做实验<span class="mojikumi-line-end">，</span>直接加上了粗略但是保证 common 和 unique 不相同的 <code>unicode<wbr>-<wbr>range</code><span class="mojikumi-line-end">，</span>具体来说就是 0 到最大的 code point<span class="mojikumi-line-end">。</span></p>
<p>这里从 0 开始也是有一定原因的<span class="mojikumi-line-end">：</span>我在看 specification 的时候注意到一个奇怪的东西叫 <a href="https://www.w3.org/TR/css-fonts-4/#first-available-font"><i>first available font</i></a><span class="mojikumi-line-end">，</span>虽然没太看懂具体是什么意思<span class="mojikumi-line-end">，</span>但大致上感觉最好是让 <code>unicode<wbr>-<wbr>range</code> 包含空格<span class="mojikumi-line-end">，</span>为了保险就从 0 开始了<span class="mojikumi-line-end">。</span></p>
<p>最后还有一个坑<span class="mojikumi-line-end">：</span>如果 composite font 中两个 <code>@font-face</code> 的 <code>unicode<wbr>-<wbr>range</code> 相交<span class="mojikumi-line-end">，</span>给交集内的字符匹配字体时<span class="mojikumi-line-end">，</span>会按出现位置的逆序进行匹配<span class="mojikumi-line-end">。</span></p>
<p>这能有什么坑呢<span class="mojikumi-line-start">（</span><span class="mojikumi-line-end">，</span>如果不动手试一试真的很难想到.. 按照整体的设计<span class="mojikumi-line-end">，</span>访问一个页面时很有可能是 common 字体已经缓存好了<span class="mojikumi-line-end">，</span>需要下载 unique 字体<span class="mojikumi-line-end">，</span>如果 unique 的 <code>@font-face</code> 放在 common 的后面<span class="mojikumi-line-end">，</span>就会优先匹配<span class="mojikumi-line-end">，</span>从而阻塞住已经缓存的 common 字体<span class="mojikumi-line-end">，</span>整个页面都要等 unique 下载好才切换字体<span class="mojikumi-line-end">，</span>而不是先显示 common 再显示剩下的 unique<span class="mojikumi-line-end">。</span>当然<span class="mojikumi-line-end">，</span>反过来的话<span class="mojikumi-line-end">，</span>unique 也会等 common<span class="mojikumi-line-end">，</span>但这样一般来说是更好的<span class="mojikumi-line-end">。</span></p>
<p>还有一些细节优化<span class="mojikumi-line-end">：</span></p>
<ul>
<li>生成 unique 字体文件时<span class="mojikumi-line-end">，</span>文件名里有 hash 就够了<span class="mojikumi-line-end">，</span>不要再添加和页面有关的信息<span class="mojikumi-line-end">，</span>这样的话如果两个页面刚好 unique glyph 集合相同就可以共用一个字体文件<span class="mojikumi-line-end">。</span></li>
<li>如果一个页面一个 common glyph 都没用<span class="mojikumi-line-end">，</span>就可以不添加 common font 的 <code>@font-face</code><span class="mojikumi-line-end">。</span></li>
</ul>
<p>代码<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/glyph-segregator/blob/master/src/generateFontFiles.ts"><code class="break-all">generateFontFiles.ts</code></a></p>
<h3 id="将-css-写入-html-文件" class="heading"><a href="#将-css-写入-html-文件" class="heading-anchor" aria-label="章节： 将 CSS 写入 HTML 文件" tabindex="-1"></a><span>将 CSS 写入 HTML 文件</span></h3>
<p>虽然也有想过直接找到 <code>&#x3C;/head></code> 文本替换<span class="mojikumi-line-end">，</span>但为了靠谱还是用的 <a href="https://github.com/jsdom/jsdom"><code>jsdom</code></a><span class="mojikumi-line-end">。</span></p>
<p>这部分的代码是最短的<span class="mojikumi-line-end">：</span><a href="https://github.com/ouuan/glyph-segregator/blob/master/src/injectCSS.ts"><code>injectCSS<wbr>.<wbr>ts</code></a></p>
<h3 id="给项目起名" class="heading"><a href="#给项目起名" class="heading-anchor" aria-label="章节： 给项目起名" tabindex="-1"></a><span>给项目起名</span></h3>
<p>主体思路是把 glyph 分成两类分别对待<span class="mojikumi-line-end">，</span>就很有 segregate 的感觉<span class="mojikumi-line-start">（</span></p>
<p>主要还是因为刚写了一个 <a href="/post/2022/11/csapp-9#segregated-fit">segregated fit</a> 印象比较深刻<span class="mojikumi-line-end">，</span>所以就取了这么个名字<span class="mojikumi-line-end">。</span></p>
<h3 id="在博客中使用" class="heading"><a href="#在博客中使用" class="heading-anchor" aria-label="章节： 在博客中使用" tabindex="-1"></a><span>在博客中使用</span></h3>
<p>就是 <a href="https://iles-docs.netlify.app/config#ssg-onsiterendered">从 iles 获取页面信息</a><span class="mojikumi-line-end">，</span>从 <a href="https://plausible.io/docs/stats-api#get-apiv1statsbreakdown">Plausible Stats API</a> 获取访问量<span class="mojikumi-line-end">，</span>调用 <a href="https://vitejs.dev/guide/api-javascript.html#preview">Vite API</a> 跑一个 preview server<span class="mojikumi-line-end">，</span>然后调用 <code>glyph<wbr>-<wbr>segregator</code><span class="mojikumi-line-end">。</span>直接看<a href="https://github.com/ouuan/iles-blog/commit/695f8b22ca3a791ceeb5440e81a6fa377b50bb3b#diff-ca899b3bf20a893bb3dfb058072a2b279953ce008f562727ad74a76d520be662">代码</a>吧<span class="mojikumi-line-end">。</span></p>
<p>访问概率的计算方式是<span class="mojikumi-line-end">，</span>看每个页面近 90 天的访客数<span class="mojikumi-line-end">，</span>加一后除以其中最高的加二<span class="mojikumi-line-start">（</span>其实这个加一加二基本没啥用<span class="mojikumi">）</span><span class="mojikumi-line-end">。</span>用最高页面访客数而非实际总访客数作分母<span class="mojikumi-line-end">，</span>是想在算法的基础上更加偏向于划分到常用字一些<span class="mojikumi-line-end">，</span>稍微优化一点访问页面较多的访客的体验<span class="mojikumi-line-end">，</span>也可以少一次 API 查询<span class="mojikumi-line-end">。</span></p>
<p>更新常用字集合用的是 <a href="https://github.com/ouuan/iles-blog/blob/master/.github/workflows/update-fonts.yml">scheduled GitHub Actions</a><span class="mojikumi-line-end">，</span>在 Docker 里跑是因为 <a href="https://stackoverflow.com/questions/74332455/connection-refused-for-local-server-in-github-actions-workflow/74341376">Connection refused for local server in github actions workflow</a><span class="mojikumi-line-end">。</span></p>
<h2 id="实际效果" class="heading"><a href="#实际效果" class="heading-anchor" aria-label="章节： 实际效果" tabindex="-1"></a><span>实际效果</span></h2>
<p>整个 <code>glyphSegregator</code> 用时 40s 左右<span class="mojikumi-line-end">，</span>glyph 数量和 common font file size (woff2) 如表所示<span class="mojikumi-line-end">：</span></p>
<div class="overflow-auto my-6"><table>
<thead>
<tr>
<th align="center"></th>
<th align="center">Noto Serif SC Regular</th>
<th align="center">Noto Serif SC Bold</th>
<th align="center">LXGW WenKai Regular</th>
<th align="center">LXGW WenKai Bold</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><span class="whitespace-nowrap">common</span></td>
<td align="center">575 (162.7KiB)</td>
<td align="center">25 (8.5KiB)</td>
<td align="center">37 (5.2KiB)</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">unique</td>
<td align="center">2311</td>
<td align="center">1536</td>
<td align="center">161</td>
<td align="center">0</td>
</tr>
</tbody>
</table></div>
<p>首页只需加载两个 Regular 的 common font<span class="mojikumi-line-end">，</span>每个页面需要加载的字体文件总大小缩减到了原来使用子集化的 1/4 左右<span class="mojikumi-line-end">，</span>并且加载的字体文件数量从 10~30 个缩减到了 2~6 个<span class="mojikumi-line-end">。</span></p>
<p>作为对比<span class="mojikumi-line-end">，</span>如果出现在两个页面就设为常用字<span class="mojikumi-line-end">，</span>Noto Serif SC Regular 的 common 有 299KiB<span class="mojikumi-line-end">，</span>出现在三个页面则是 227KiB<span class="mojikumi-line-end">。</span></p>
<h2 id="后记：font-subsetting-与-kerning" class="heading"><a href="#后记：font-subsetting-与-kerning" class="heading-anchor" aria-label="章节： 后记：font subsetting 与 kerning" tabindex="-1"></a><span>后记<span class="mojikumi-line-end">：</span>font subsetting 与 kerning</span></h2>
<p>因为通过 Stylus 设置了不使用本地字体<span class="mojikumi-line-end">，</span>我可以轻松地在本地字体和 web font 之间切换<span class="mojikumi-line-end">。</span>然后我就偶然发现<span class="mojikumi-line-end">，</span>切换时一些标题发生了字符的偏移<span class="mojikumi-line-end">。</span>原因也很简单<span class="mojikumi-line-end">，</span>就是相邻的两个字符本来有 kerning (字距调整)<span class="mojikumi-line-end">，</span>但它们被划分到了不同的子集中<span class="mojikumi-line-end">，</span>生成字体子集时就丢失了 kerning 信息<span class="mojikumi-line-end">。</span></p>
<p>我在 glyph-segregator 中 <a href="https://github.com/ouuan/glyph-segregator/commit/ae3ddd5cbb91f0eb6bbfec7071ee408c28b36dec">添加了 <code class="break-all">alwaysCommonGlyphs</code> 选项</a><span class="mojikumi-line-start">（</span>ASCII 字符总是设为 common<span class="mojikumi-line-end">，</span>如果需要处理非 ASCII 字符的 kerning 也可以修改设置<span class="mojikumi-line-end">，</span>但我的博客就假设只有 ASCII 字符会遇到这个问题<span class="mojikumi-line-end">）</span>来解决这一问题<span class="mojikumi-line-end">。</span>最好的解决方案是真的去看一下字体里有哪些 kerning 信息然后相应地处理<span class="mojikumi-line-end">，</span>但是差不多得了<span class="mojikumi-line-start">（</span></p>
<a id="font-kerning-示例" name="font-kerning-示例" aria-hidden="true"></a>
<aside role="note" data-v-a2ab257f><div class="shadow-md rd-1 b-l-6 my-6 bg-blue-1 dark:bg-blue-9 b-blue" data-v-a2ab257f><div class="p-3 flex justify-between items-center" data-v-a2ab257f><h3 class="flex items-center gap-1 font-bold" data-v-a2ab257f><span class="text-5 i-mdi-pencil text-blue" data-v-a2ab257f></span><span class="sr-only" data-v-a2ab257f>Note: </span><span data-v-a2ab257f>font kerning 示例</span></h3><!--v-if--></div><div class="overflow-auto rd-br-1 bg-card px-6 dark:bg-bghover" data-v-a2ab257f><ul>
<li>有 kerning: <span style="font-kerning: normal; font-size: 2rem;">TAVeYoW</span></li>
<li>没 kerning: <span style="font-kerning: none; font-size: 2rem;">TAVeYoW</span></li>
</ul></div></div></aside>
<p>后记的后记<span class="mojikumi-line-end">：</span>还是改成了<a href="https://github.com/ouuan/glyph-segregator/commit/e955eb1c935d77adf0e4bcb5c870fee0614801b6">即使未被使用也添加进 common glyph set</a><span class="mojikumi-line-end">，</span>否则如果新加一个 always common 的 glyph<span class="mojikumi-line-end">，</span>为了让它 common<span class="mojikumi-line-end">，</span>就会改变 common glyph set<span class="mojikumi-line-end">，</span>导致 cache 失效<span class="mojikumi-line-end">。</span>这里有一定的 trade-off<span class="mojikumi-line-end">，</span>要在 cache 失效 / 多塞一些未被使用的 glyph / kerning 挂掉之间进行选择<span class="mojikumi-line-end">，</span>我还是选择了多塞一些未被使用的 glyph<span class="mojikumi-line-end">，</span>毕竟整个 ASCII 也没多大<span class="mojikumi-line-end">。</span>最好是借助人类智慧来预测一下哪些 glyph 更有可能在未来被添加而放进 common glyph set 里<span class="mojikumi-line-end">，</span>其他 glyph 就只有使用了才放<span class="mojikumi-line-end">，</span>但是差不多得了<span class="mojikumi-line-start">（</span></p>]]></content:encoded>
            <category domain="https://ouuan.moe/tag/blog">blog</category>
            <category domain="https://ouuan.moe/tag/Web">Web</category>
            <category domain="https://ouuan.moe/tag/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91">项目开发</category>
        </item>
    </channel>
</rss>