自动处理网页里的全角引号和标点挤压

全角引号

在 Unicode 里问号叹号各种括号都有全角半角两种版本各自有独立的编码但因为莫名的原因最常用的引号却不在此列中英混排的时候想要正确显示直角和半角的引号就很头疼搞不好的话中文里显示半角引号还不算太违和英文里蹦出来一个全角引号就太丑了

CSS 没法自动区别什么时候用全角引号什么时候用半角只能靠标记好在还没复杂到需要手工标记的地步只要用程序检查引号前后的字是中文还是英文以此标记全角还是半角就基本不会出错我现在的办法是这样默认字体还是英文先中文后

body {
  font-family: Charter, Source Han Serif CN, serif;
}

需要全角的引号用 span 标签包起来

<span class="full-width-quote">“</span>

然后用 CSS 指定中文字体

span.full-width-quote {
  font-family: Srouce Han Serif CN, serif;
}

怎么区别一个引号应该全角还是半角呢我用了一个简单的判断方法如果前或后紧挨着中文字符就全角如果前后都不是中文字符就半角我目前还没发现这个简单判断不够用的情况这样一来还需要判断一个字符是不是中文最简单的办法是检查字符的 Unicode codepoint 在不在中文区间内常用汉字和标点符号在 0x4E000x9FFF0x30000x303F 两个区间里检查这两个就够了其他的区间里都是生僻字

标点挤压

全角引号搞好了又会贪心标点挤压没有标点挤压的时候几个标点排在一起确实不大好看

没有标点挤压的样子
余日摇滚第48期

挤压以后就不那么空了

有标点挤压的样子
余日摇滚第48期

原理是设置 CSS 属性 font-feature-settings: "halt"启用 OpenType 的 halt 特性和全角引号一样用程序自动识别需要挤压的标点包在 span 标签里要注意的是你用的字体要有 halt 这个特性才行我用的思源宋体是有的

具体怎么挤压标点符号我没找到现成的标准或者算法下面是我的方法这个方法并不完整只处理比较常见的情况但对我来说够用了如果读者知道更好的算法请一定告诉我

首先能挤压的标点符号可以分为三类靠左靠右居中

各种类型的标点符号
中文排版需求W3C Working Draft 01 November 20203.1.6 标点符号的宽度调整有修改

我们不考虑居中的符号因为简体中文普遍不用而我以简体中文写作程序从头到尾遍历每个字符决定每个字符要不要挤压挤不挤压取决于这个字符和其前后的字符以伪码表达为

遍历 字符:
  如果 此字符为靠左标点 且 后一字符为标点:
    挤压此字符
  如果 此字符为靠右标点 且 前一字符为靠右标点:
    挤压此字符

这个算法运行的结果是这样文字

如果你用 pyftsubset 压缩过字体文件1注意它默认会把 halt 这样的 OTF 特性扔掉这样一来即使加上挤压标签也没有效果压缩的时候加上 --layout-features='*' 这个选项就可以保留所有 OTF 特性了也可以用 --layout-features='halt' 只保留 halt 特性

破折号

我还发现破折号有时会显示成 em dash因为破折号在 Unicode 里其实就是 em dash解决方法和全角引号一样包上全角的 span 标签就可以了——这样就能正确显示破折号