2019年9月8日 星期日

為中文字體檔瘦身

在開發網頁的過程中,尤其是希望用到特別的中文字體時,由於中文字體檔案動輒有數十 MB 大小,下載需要一點時間。因此通常都會以圖片代替文字。用圖片代替文字的短處是無法把文字拷貝、只有單一尺寸、修改起來麻煩...等。究竟有沒有其他方法?

如果能把用不著的文字從字體檔中刪除,檔案體積將會縮小,變成適合在網頁中加載。在 Node.js 中有一個名為「fontmin」的模組可以滿足到這個需求。在 Terminal 輸入:
npm install fontmin gulp-rename fs
它會安裝程式所需要的模組。

以下是我的瘦身程式:
//----------------------------------------------------------------------------------------
//  Font File Minifier
//----------------------------------------------------------------------------------------
//  Written by : Pacess HO
//  Copyright Pacess Studio, 2019.  All rights reserved.
//----------------------------------------------------------------------------------------

//  Require modules
var _GULPRENAME = require("gulp-rename");
var _FONTMIN = require("fontmin");
var _FS = require("fs");

//----------------------------------------------------------------------------------------
//  Global variables
var _now = new Date();
var year = _now.getFullYear().toString();
var month = _now.getMonth().toString();
var date = _now.getDate().toString();
var hours = _now.getHours().toString();
var minutes = _now.getMinutes().toString();
var seconds = _now.getSeconds().toString();

month = (month.length === 2) ? month : "0"+month;
date = (date.length === 2) ? date : "0"+date;
hours = (hours.length === 2) ? hours : "0"+hours;
minutes = (minutes.length === 2) ? minutes : "0"+minutes;
seconds = (seconds.length === 2) ? seconds : "0"+seconds;

//========================================================================================
//  Program start
console.log("\n##----------------------------------------------------------");
console.log("##  Font File Minifier");
console.log("##  Written by Pacess HO");
console.log("##  Copyright Pacess Studio, 2019.  All rights reserved.");
console.log("##----------------------------------------------------------\n");

//----------------------------------------------------------------------------------------
//  Reading parameters
var argu = process.argv.slice(2);
var inputFontFilename = "NotoSansTC-Medium.ttf";
var arguGlyphs = "";

for (var i=0; i<argu.length; i++)  {
   switch (argu[i])  {

      case "-f":
      case "--font":  {
         inputFontFilename = argu[i+1];
      }  break;

      case "-g":
      case "--glyphs":  {
         arguGlyphs = argu[i+1];
      }  break;
   }
}

if (arguGlyphs == "")  {

 //  No glyphs provided, show instruction
   console.log("Usage:");
   console.log("   node fontminifier.js -g <characters> -f <fontfile>\n");
   console.log("Options:");
   console.log("   --glyphs");
   console.log("   -g      = Characters that you want to keep in font file.\n");
   console.log("   --font");
   console.log("   -f      = Font source filename in TTF format with extension\n");
   console.log("Example:");
   console.log("   node fontminifier.js -g 0123456789 -f Arial.ttf\n");
   process.exit(1);
}

//----------------------------------------------------------------------------------------
//  Main process
console.log("Input font file: "+inputFontFilename);
console.log("Glyphs to be keep: "+arguGlyphs);

//  Font file name
var fontFilename = inputFontFilename.split(".");
var outputFilename = fontFilename[0]+"_"+year+month+date+hours+minutes+seconds+".ttf";

//  Set the files to be optimized
var fontPath = "./"+inputFontFilename;

//  Set the destination folder to where your files will be written
var outputPath = "./";

//  Check if font file is exists.
if (_FS.existsSync(fontPath) == false)  {

   //  If do not exists then will stop the process
   console.error("###  Font file not found...\n");
   process.exit(1);
}

//  Set up by fontmin
var fontmin = new _FONTMIN()
 .use(_GULPRENAME(outputFilename))
 .src(fontPath)
 .dest(outputPath);

if (arguGlyphs != "")  {
 console.log("\nProcessing...");
 fontmin.use(_FONTMIN.glyph({
  text: arguGlyphs,
  hinting: false
 }));
}

//  Start minifying font with the given settings.
fontmin.run(function(err, files)  {
 if (err)  {
  console.error("###  Something went wrong...\n");
  throw err;
 }

 console.log("Minify done: "+outputPath+outputFilename+"\n");
 process.exit(0);
});

讓我們來了解程式的運作。
//  Require modules
var _GULPRENAME = require("gulp-rename");
var _FONTMIN = require("fontmin");
var _FS = require("fs");
程式最開端是載入三個需要的模組。「gulp-rename」是用於更改檔案名稱;「fontmin」是字體瘦身的核心;「fs」主 要是檔案相關的處理。  
//  Global variables
var _now = new Date();
var year = _now.getFullYear().toString();
var month = _now.getMonth().toString();
var date = _now.getDate().toString();
var hours = _now.getHours().toString();
var minutes = _now.getMinutes().toString();
var seconds = _now.getSeconds().toString();

month = (month.length === 2) ? month : "0"+month;
date = (date.length === 2) ? date : "0"+date;
hours = (hours.length === 2) ? hours : "0"+hours;
minutes = (minutes.length === 2) ? minutes : "0"+minutes;
seconds = (seconds.length === 2) ? seconds : "0"+seconds;
向系統取得當刻的時間,並把數值分別設定在對應的變量中。  
var argu = process.argv.slice(2);
var inputFontFilename = "NotoSansTC-Medium.ttf";
var arguGlyphs = "";

for (var i=0; i<argu.length; i++)  {
   switch (argu[i])  {

      case "-f":
      case "--font":  {
         inputFontFilename = argu[i+1];
      }  break;

      case "-g":
      case "--glyphs":  {
         arguGlyphs = argu[i+1];
      }  break;
   }
}

if (arguGlyphs == "")  {

 //  No glyphs provided, show instruction
   console.log("Usage:");
   console.log("   node fontminifier.js -g <characters> -f <fontfile>\n");
   console.log("Options:");
   console.log("   --glyphs");
   console.log("   -g      = Characters that you want to keep in font file.\n");
   console.log("   --font");
   console.log("   -f      = Font source filename in TTF format with extension\n");
   console.log("Example:");
   console.log("   node fontminifier.js -g 0123456789 -f Arial.ttf\n");
   process.exit(1);
}
這段是讀取參數,如果是「-f」或「--font」就把數值儲存在「inputFontFilename」;如果是「-g」或「--glyphs」就把數值儲存在「arguGlyphs」。當沒有指定字體檔名時,會使用預設的「NotoSansTC-Medium.ttf」字體檔案。 由於這是一個瘦身程式,如果沒有指定保留哪些字元時,運作便會變得沒有意義,因此程式會停止執行。也有可能是用戶不清楚使用方法,所以同時會顯示使用說明。  
//  Main process
console.log("Input font file: "+inputFontFilename);
console.log("Glyphs to be keep: "+arguGlyphs);

//  Font file name
var fontFilename = inputFontFilename.split(".");
var outputFilename = fontFilename[0]+"_"+year+month+date+hours+minutes+seconds+".ttf";

//  Set the files to be optimized
var fontPath = "./"+inputFontFilename;

//  Set the destination folder to where your files will be written
var outputPath = "./";

//  Check if font file is exists.
if (_FS.existsSync(fontPath) == false)  {

   //  If do not exists then will stop the process
   console.error("###  Font file not found...\n");
   process.exit(1);
}
接下來是依照參數數值去準備好輸入路徑、輸出路徑、路徑檔名。同時也會檢查指定的字體檔案是否存在。沒有字體檔案會甚麼也做不來,這時就需要顯示錯誤訊息。  
//  Set up by fontmin
var fontmin = new _FONTMIN()
 .use(_GULPRENAME(outputFilename))
 .src(fontPath)
 .dest(outputPath);

if (arguGlyphs != "")  {
 console.log("\nProcessing...");
 fontmin.use(_FONTMIN.glyph({
  text: arguGlyphs,
  hinting: false
 }));
}
一切就緒,現在要建立核心模組,並把準備好的數值通知模組。  
//  Start minifying font with the given settings.
fontmin.run(function(err, files)  {
 if (err)  {
  console.error("###  Something went wrong...\n");
  throw err;
 }

 console.log("Minify done: "+outputPath+outputFilename+"\n");
 process.exit(0);
});
最後就是執行瘦身動作。如果當中出現錯誤時,就顯示錯誤訊息。成功的話則顯示輸出檔名。要執行程式,在「Terminal」輸入:  
node fontminifier.js -g 0123456789 -f phosphate.ttf
「-g」參數後面的是要保留的文字。「-f」參數後面的是輸入的字體檔案名稱;程式會在檔案名稱後加入當刻時間,作為 輸出的字體檔案名稱。留意:程式只支援 TTF 格式,TTC, OTF,...等是不支持。這段指令的意思是讀取 「phosphate.ttf」字體檔,只保留「0123456789」字符,其他的都刪除掉。完成後會有一個類似 「phosphate_YYYYMMDDHHIISS.ttf」的檔案出現;它就是瘦身後的字體檔案。

沒有留言: