H5頁面快速搭建之高級字體應用實踐

原文出處: 淘寶前端團隊(FED)- 龍馭   

H5頁面快速搭建之高級字體應用實踐

背景

  • 最近在開發一個 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 片段存入數據庫

     

     

     

     

     

     

    JavaScript

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    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 片段

 

 

 

 

 

 

JavaScript

 

1

2

3

4

5

<?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

     

     

     

     

     

    JavaScript

     

    1

    2

    3

    4

    5

    6

    7

    8

    //創建空白字體,使用 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 動畫等),需要考慮兼容性和漸進方案。

QQ技術交流羣290551701 http://cxy.liuzhihengseo.com/543.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章