H5页面快速搭建之高级字体应用实践

来源:淘宝前端团队(FED)- 龙驭

链接:http://taobaofed.org/blog/2016/04/12/webfont-practice/

H5页面快速搭建之高级字体应用实践_第1张图片


背景

  • 最近在开发一个 H5 活动页快速搭建平台,可以通过拖拽编辑图片,文字等元素组件,快速搭建出一个移动端的活动页面,基本交互和成品效果类似 PPT 软件。这类活动大量在微信等平台上传播,其中会包含各种动画和特效,而各类高级艺术字体(如:方正兰亭黑,方正彩云,方正大草,方正剑体等)的应用也非常广泛。

  • 之前用户只能通过 ps 等软件将文字转化为图片再贴到平台上使用。使用成本很高,修改,调试都非常不便,而且图片占用的资源也比较多,为了降低用户的使用成本,基于一站式搭建的理念,我们需要将高级字体的使用透明化,使用户和使用 PPT 一样直接选择字体使用即可。

  • 技术选型

  • 第一种方案是通过用户输入的文字,和选择的字体,通过服务器生成对应的图片来使用。这种方案的优点是逻辑简单,缺点是搭建/修改时增加了复杂度,调试时无法实时预览文字在活动中的效果。而且容易出现大量冗余图片,最终页面的图片请求也会增加。

  • 第二种方案是调用 iconfont.cn 的服务接口,通过传递字体和文字内容来获取字体文件。这种方案的优点是可以直接利用现有成熟平台,开发成本低,可靠。缺点是增加了外部依赖,不但面临合作方配合的限制,而且无法自行控制可供的选择字体等。

  • 最终采用的的第三种方案是直接使用 iconfont.cn 的 Node.js 模块 (font-carrier) ,自行解析/生成字体,将生成的字体放在我们自己申请的 OSS 中存储使用。这种方法的开发量最大,且要消耗额外的 OSS 资源,但是整个流程独立自主,可以不断定制优化,自行添加字体等,由于我们的服务只面向移动端,所以只需要生成 ttf 或者 woff 一种文件类型即可兼容。

  • 字体文件解析的基本原理

    字体文件的核心结构

    以 ttf 文件为例,字体文件中主要包含了字体头表,位置索引表和图元数据表等等,其中最核心的部分就是图元数据表,也就是字形描述表,它可以包含可变数目的图元,每个图元可以有不同数目的控制点,甚至还可以有数量可变的图元指令,通过位置索引表对应到每个字符上,通过图元数据表,使其只包含需要使用的字符的图元描述。即可最小化字体,使其可用于生产环境的页面中,其他类型的字体文件(如 woff, eot, svg 等)原理也是大同小异,仅仅是压缩方式和字形描述规范不同,也可以互相转化。

    font-carrier 模块基本原理

    font-carrier 模块使用 OpenType 模块分析 ttf 文件,可以文件的内容脚本化,使其成为一个字符 unicode 编码和其字形描述的键值对象。通过对这个对象的 min 方法,可以使其最小化,并且再逆向生成文件 Buffer 供用户使用。

    一期实现流程

  • 在程序启动后通过 font-carrier 模块将本地的字体文件包装成字体对象,保存在服务器内存中。

  • 用户保存页面时,记录下此活动所有使用的高级字体和相应的文字内容

  • 通过 font-carrier 模块找到字体对应的字体对象,使用 min 命令生成最小化的字体对象

  • 使用 min 命令生成缩小后的字体文件,保存到 OSS,并以活动的 id 为路径,字体的名字为文件名。

  • 最终渲染时通过记录的活动使用的字体名拼出 OSS 路径来引用文件

  • 存在问题

  • 由于字体数量较多,启动时将本地字体文件包装成字体对象的时间非常长,可达到数十分钟。

  • 字体对象常驻内存,占用巨大,甚至可能直接吃光内存

  • 分析问题

    因为 font-carrier 模块生成的字体对象无法通过文件来持久化保存,只能生成后常驻内存中,而字体的数量多,大小也大,所以不管是生成的时间,生成时消耗的性能,生成后占用的内存都非常巨大。所以问题的关键在于如何把字体的分析结果持久化保存在服务器中。

    解决方案

    在咨询了 font-carrier 模块的开发者后,了解到 font-carrier 模块还有生成字体的 svg 片段的方法,可以将字体的图元数据转变为 svg 输出,并可以将 svg 逆向导入到空字体文件中来生成最终字体文件。

    通过将字体分析转译后的 svg 片段结果保存在数据库中,即可持久化分析结果。使用的时候通过创建空字体->配置字符-svg 的对应关系->提取字体->上传到 OSS 的流程来使用最小化后的字体即可。

    二期实现流程

    建立提取字体任务,运行时遍历字体文件,提取其中的 svg 片段存入数据库

    var transFont = fontCarrier.transfer(__dirname + '/../../www/fonts/' + fontInfo.font_name + '.ttf');

    var words = [];

    _.each(transFont.__glyphs, function(n, word) {

    words.push({

     word: word,

     fontId: fontInfo.id,

     svg: transFont.getSvg(word, {

       skipViewport: true

     })

    });

    });


    以下是一段方正喵呜体中的“我”字提取的 svg 片段


     

     

     

     


    保存活动时创建空字体,导入需要的字符和其对应的 svg,并将这个字体保存到 OSS

    //创建空白字体,使用 svg 生成字体

    var font = fontCarrier.create();

    values.forEach(function(v) {

      font.setSvg(v.word,v.svg);

    });

    return font.output({

      types:['woff']

    })['woff'];


    最终渲染时通过的记录的活动使用的字体名拼出 OSS 路径来引用文件

    新的问题

    在正常运行了一段时间后,用户反馈了新的问题,编辑和预览时的字宽度不匹配,现象为所有的字符都变为了全角模式,数字,字母和符号,都占用了一个汉字的位置。如图:

    分析问题

    经过排查和测试,最后发现原因在于生成 svg 片段时,模块给这个 svg 加上了宽和高,这是不必要的,在显示汉字等全角字符时,一切正常,而在显示半角字符时,则会导致两边出现空隙。

    解决方案

    在无法改变 font-carrier 模块的前提下,只能在我们自己的流程中加补丁,我在读取 svg 使用前,额外增加了替换代码将宽高删除,证明可以解决该问题。另外我也知会了模块开发者,在未来的版本中修复此问题。修复后效果如图:

    H5页面快速搭建之高级字体应用实践_第2张图片


    未来展望

  • 目前我们采用引用字体文件的方式来定义高级字体,而最近团队的无线端最佳实践的要求,无线端使用的字体将字体文件 base 64 化,以减少请求数,未来我们也将改造成这种方式,不但符合最佳实践的要求,同时还可以节省 OSS 存储的资源。

  • 下一阶段我们将调研 svg 在移动端的兼容性和性能,未来开发的插入几何形状功能将考虑使用这一技术,同是我们也会尝试直接用 svg 绘制字体,产生更多的可能性(比如 svg 动画等),需要考虑兼容性和渐进方案。

  • 我们是一群热爱IT的年轻人,如果你也爱IT、爱移动端开发,欢迎加入我们,让我们共同为梦想发声。

    关注蓝鸥(lanou3g),推送IT新知识与资讯,让你每天进步一点点。

    PS:喜欢你就点个赞,有用你就收进后宫,认识程序员你就转发一下辣。

    你可能感兴趣的:(H5页面快速搭建之高级字体应用实践)