找回密码
 立即注册
首页 业界区 业界 通过连字从纯ASCII渲染化学式

通过连字从纯ASCII渲染化学式

予捻 2026-2-7 14:55:01
化学式利用上下标来表示物质中的原子组成和电荷状态。在通常情况下,在排版时输入化学式需要使用额外的Unicode字符(⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻、₀₁₂₃₄₅₆₇₈₉),或者手动应用上下标样式,或者使用专门的语法(比如TeX)。
然而,使用常规键盘输入上下标字符并不方便,渲染TeX则往往需要繁重的依赖。有没有办法可以在不用TeX的情况下使用类似TeX语法绘制上下标呢?答案是有的,我们可以借助TrueType/OpenType字体的连字功能实现。
使用连字渲染化学式

现代字体技术允许我们通过字体的连字(ligature)功能来根据上下文替换字符的字形。该功能最初是用于 f + i = fi 这样的排版渲染需求,以及用于渲染像阿拉伯语这样字形和上下文强相关的文字。许多编程字体中也通过连字来渲染多字符操作符(比如将!=渲染为 ≠)。
通过连字特性,我们可以实现纯ASCII化学式的渲染,只要定义以下连字规则:

  • 当数字出现下标标记符_之后时,使用下标字形
  • 当数字或+、-符号出现在上标标记符^之后时,使用上标字形
为了方便,我们还可以规定

  • 当数字出现在下标字形之后时,维持下标字形
  • 当数字或+、-符号出现在上标字形的数字之后时,维持上标字形
我们可以通过FontTools库来为现有字体添加这些连字规则。下面的Python脚本实现了这个功能:
build_chem_font.py
  1. #!/usr/bin/env python3
  2. from fontTools.ttLib import TTFont
  3. from fontTools.ttLib.tables._g_l_y_f import Glyph, GlyphComponent
  4. from fontTools.feaLib.builder import addOpenTypeFeatures
  5. from fontTools.pens.recordingPen import RecordingPen
  6. from fontTools.pens.transformPen import TransformPen
  7. from fontTools.pens.ttGlyphPen import TTGlyphPen
  8. import io
  9. def build_chem_font(font):
  10.     glyf, hmtx = font["glyf"], font["hmtx"]
  11.     gord, cmap = font.getGlyphOrder(), font.getBestCmap()
  12.     gset = font.getGlyphSet()
  13.     upm = font["head"].unitsPerEm
  14.     def register_glyph(name, glyph_obj, width, lsb):
  15.         glyf[name] = glyph_obj
  16.         hmtx[name] = (width, lsb)
  17.         if name not in gord:
  18.             gord.append(name)
  19.     empty_glyph = Glyph()
  20.     empty_glyph.numberOfContours = 0
  21.     register_glyph("hide.glyph", empty_glyph, 0, 0)
  22.     def build_derivative(src, scale=1, xoff=0, yoff=0):
  23.         rec = RecordingPen()
  24.         gset[src].draw(rec)
  25.         tt_pen = TTGlyphPen(gset)
  26.         t_pen = TransformPen(tt_pen, (scale, 0, 0, scale, xoff, yoff))
  27.         rec.replay(t_pen)
  28.         return tt_pen.glyph()
  29.     caret, lowline = cmap.get(ord("^")), cmap.get(ord("_"))
  30.     nums = [cmap[ord(c)] for c in "0123456789"]
  31.     pm = [cmap[ord(c)] for c in "+-"]
  32.     for g in nums + pm:
  33.         gl = build_derivative(g, 0.6, 0, int(upm * 0.35))
  34.         register_glyph(f"{g}.sup", gl, int(hmtx[g][0] * 0.6), 0)
  35.     for g in nums:
  36.         gl = build_derivative(g, 0.6, 0, int(upm * -0.1))
  37.         register_glyph(f"{g}.sub", gl, int(hmtx[g][0] * 0.6), 0)
  38.     font.setGlyphOrder(gord)
  39.     feature = f"""
  40.     @supprefix = [{caret} {" ".join([c+ ".sup" for c in nums])}];
  41.     @subprefix = [{lowline} {" ".join([c+ ".sub" for c in nums])}];
  42.     @supchar0 = [{" ".join(nums + pm)}];
  43.     @subchar0 = [{" ".join(nums)}];
  44.     @supchar = [{" ".join([c+ ".sup" for c in nums + pm])}];
  45.     @subchar = [{" ".join([c+ ".sub" for c in nums])}];
  46.     feature calt {{
  47.         lookup SUB_CHAIN {{ sub @subprefix @subchar0' by @subchar; }} SUB_CHAIN;
  48.         lookup SUP_CHAIN {{ sub @supprefix @supchar0' by @supchar; }} SUP_CHAIN;
  49.         lookup HIDE_CARET {{ sub {caret}' @supchar by hide.glyph; }} HIDE_CARET;
  50.         lookup HIDE_LOWLINE {{ sub {lowline}' @subchar by hide.glyph; }} HIDE_LOWLINE;
  51.     }} calt;
  52.     """
  53.     with io.StringIO(feature) as fea:
  54.         addOpenTypeFeatures(font, fea)
  55. if __name__ == "__main__":
  56.     import argparse
  57.     parser = argparse.ArgumentParser(description="Create chemical symbols font.")
  58.     parser.add_argument("input", help="Path to input file (TTF font)")
  59.     parser.add_argument("output", help="Path to output file")
  60.     args = parser.parse_args()
  61.     font = TTFont(args.input)
  62.     build_chem_font(font)
  63.     font.save(args.output)
复制代码
通过python3 build_chem_font.py input.ttf chem.ttf命令运行该脚本,即可生成一个支持化学式渲染的字体文件chem.ttf。之后可以在浏览器环境中通过css@font-face加载字体查看结果:
preview.html
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>font preview</title>
  6.    
  7. </head>
  8. <body>
  9.     <textarea id="preview">H^+ + OH^- = H_2O
  10. Ag^+ + Cl^- = AgCl
  11. Ba^2+ + SO_4^2- = BaSO_4
  12. Fe^3+ + 3OH^- = Fe(OH)_3
  13. CO_3^2- + 2H^+ = H_2O + CO_2
  14. 2Al + 6H^+ = 2Al^3+ + 3H_2
  15. Cu^2+ + Fe = Fe^2+ + Cu
  16. CH_4 + 2O_2 = CO_2 + 2H_2O
  17. C_2H_5OH + 3O_2 = 2CO_2 + 3H_2O
  18. CH_2=CH_2 + Br_2 = CH_2BrCH_2Br
  19. CH_3COOH + C_2H_5OH = CH_3COOC_2H_5 + H_2O
  20. C_6H_6 + HNO_3 = C_6H_5NO_2 + H_2O
  21. C_6H_12O_6 + 6O_2 = 6CO_2 + 6H_2O
  22. 2CH_3OH + 3O_2 = 2CO_2 + 4H_2O
  23. NH_4^+ + OH^- = NH_3 + H_2O
  24. 2I^- + Cl_2 = I_2 + 2Cl^-
  25. MnO_4^- + 5Fe^2+ + 8H^+ = Mn^2+ + 5Fe^3+ + 4H_2O
  26. Cr_2O_7^2- + 6Fe^2+ + 14H^+ = 2Cr^3+ + 6Fe^3+ + 7H_2O
  27. CH_3CH_2OH + CuO = CH_3CHO + Cu + H_2O</textarea>
  28. </body>
  29. </html>
复制代码
最终的渲染结果如下:
1.png

注:本方法修改了字体文件,操作前请先参考字体的授权规则和用户协议,避免未授权的编辑。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2026-2-8 08:12:22

举报

2026-2-10 20:52:36

举报

您需要登录后才可以回帖 登录 | 立即注册