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

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

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


背景

  • 最近在开发一个 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 片段


    <?xml version="1.0" encoding="utf-8"?>

      <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

      <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="100px" height="100px" viewBox="0 0 1000 1000">

      <path d="M324 857Q324 837 332 819 340 801 340 775 317 775 296.5 776.5 276 778 253 778 237 778 224.5 769.5 212 761 212 742 212 731 221 722 230 713 242 712L333 703Q348 703 353 691 357 662 357.5 643 358 624 358 596L358 559Q357 554 353 554 351 554 349 554 313 554 278 558.5 243 563 208 563 193 563 182 557 171 551 171 533.5 171 516 184 507 197 498 217.5 494 238 490 261 489 286 489 306.5 488.5 327 488 342 487 357 486 358 479L358 461Q358 458 356.5 434.5 355 411 352 384 349 357 344.5 335 340 313 333 313 332 313 329 315 326 317 325 318L270 372Q265 377 259 378 255 381 248 381 217 381 217 348 217 340 218 334 219 328 225 322L420 132Q426 125 432 124 438 123 446 123 460 123 469.5 130.5 479 138 479 152 479 160 474 168 471 176 465 181 462 185 452.5 194.5 443 204 432 214.5 421 225 411.5 234.5 402 244 399 247 398 249 397 252 396 255 395 256L395 259 395 260Q402 290 408 315 414 340 417.5 364.5 421 389 423.5 414.5 426 440 428 471L433 479Q433 480 442 480 451 480 456 480 475 480 492 479.5 509 479 528 476L528 449Q528 399 523.5 348.5 519 298 519 247 519 228 523 215 529 201 550 201 565 201 573 209.5 581 218 584 231 587 244 587.5 257.5 588 271 589 281 589 287 591 310 593 335 594 362 595 389 596 412 598 437 598 442 598 447 598 451 598 457 600 462L602 476 611 479 723 479Q742 480 759.5 486.5 777 493 777 515 777 526 770.5 536 764 546 752 546L628 546 615 550Q614 551 614 553 614 557 614 559 614 564 619 583 623 604 629 625 635 646 642.5 663 650 680 656 680 666 680 673 667 682 655 691 639 701 625 714 611 726 599 743 599 756 599 766.5 606.5 777 614 777 629 777 642 764.5 658.5 752 675 736 692 722 709 709 723 697 739 697 747L697 748Q697 749 698 749 704 756 710 764 718 773 726.5 780.5 735 788 745 794 755 800 764.5 800 774 800 782 796 790 794 799 794 812 794 821 805 830 816 830 829 830 851 812 862 796 873 777 873 755 873 736.5 866 718 859 701.5 847 685 835 669.5 821 654 807 640 795 631 800 623.5 806.5 616 813 609 818.5 602 824 593.5 828.5 585 833 575 833 544 833 544 803 544 798 544.5 794.5 545 791 548 786L598 737 598 725Q576 688 562.5 642 549 596 540 554 538 550 532.5 548 527 546 522 546L519 546Q514 546 503 546 493 548 481.5 548.5 470 549 459.5 549.5 449 550 445 550 442 552 438.5 553.5 435 555 433.5 558.5 432 562 429 574 428 588 428 591 428 596 427.5 607.5 427 619 426 632.5 425 646 424 657 424 670 424 674 424 681 426 682 428 683 432 683 444 683 454.5 681 465 679 477.5 679 490 679 500.5 686.5 511 694 511 708 511 727 499 733 486 741 470 744 454 747 439.5 749 425 751 420 761 419 763 417.5 767.5 416 772 416 774 415 779 411.5 791.5 408 804 404.5 817.5 401 831 398 843 396 857 395 861 385 886 357 886 343 886 333.5 878.5 324 871 324 857M668 269Q668 254 677 246 687 240 699 240 716 240 726 251 736 262 743 277 745 281 750 292 755 303 760 316 765 329 769.5 339.5 774 350 777 355L777 369Q777 385 770.5 393.5 764 402 746 402 735 402 721.5 385 708 368 696.5 346 685 324 676 301 668 280 668 269Z"/>

      </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 使用前,额外增加了替换代码将宽高删除,证明可以解决该问题。另外我也知会了模块开发者,在未来的版本中修复此问题。修复后效果如图:


    未来展望

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

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

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

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

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

    最后编辑于
    ©著作权归作者所有,转载或内容合作请联系作者
    • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
      沈念sama阅读 206,602评论 6 481
    • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
      沈念sama阅读 88,442评论 2 382
    • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
      开封第一讲书人阅读 152,878评论 0 344
    • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
      开封第一讲书人阅读 55,306评论 1 279
    • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
      茶点故事阅读 64,330评论 5 373
    • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
      开封第一讲书人阅读 49,071评论 1 285
    • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
      沈念sama阅读 38,382评论 3 400
    • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
      开封第一讲书人阅读 37,006评论 0 259
    • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
      沈念sama阅读 43,512评论 1 300
    • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
      茶点故事阅读 35,965评论 2 325
    • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
      茶点故事阅读 38,094评论 1 333
    • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
      沈念sama阅读 33,732评论 4 323
    • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
      茶点故事阅读 39,283评论 3 307
    • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
      开封第一讲书人阅读 30,286评论 0 19
    • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
      开封第一讲书人阅读 31,512评论 1 262
    • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
      沈念sama阅读 45,536评论 2 354
    • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
      茶点故事阅读 42,828评论 2 345

    推荐阅读更多精彩内容

    • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
      卡卡罗2017阅读 134,599评论 18 139
    • 一、概念 参考网页字体Serif和Sans-serif的区别及浏览器字体的设置CSS Font知识整理总结 1.F...
      合肥黑阅读 6,077评论 0 12
    • Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意...
      萤火虫de梦阅读 99,157评论 9 467
    • 最近兴致上来,就想更换了那Blog标题字体(汉字的);网上搜索了一番,发现蘇新詩柳繁體这款甚合我心;然后就着手搞将...
      晚晴幽草阅读 2,358评论 1 8
    • 山门外,虫鸣鸟叫,流水潺潺,阳光照耀着大地,春风轻摇着草木,恰似一曲无邪的天籁。 他早已了却了尘世的俗缘,却唯独...
      法号觉隐阅读 364评论 0 1