2019年3月25日 星期一

FreeType的字型排版資訊

FreeType的字型排版資訊

前言

  在之前的初探FreeType中讀取出字型的pixel資料,但只是這樣的資料並不夠用,只是畫出單一個字還可以,如果要畫一個字串的話,就需要知道"排版資訊",在此做個紀錄。

內容

  在之前的"初探FreeType"中,每個"glyph"的寬跟高都是不同的,要畫出一個左排到右的字串的話,會發現無法對齊,因為只知道該字的Pixel寬與高並沒有辦法對齊。

  排版的資訊在"FT_load_Glyph()"時就會存在"face"裡,個人整理了排版資訊如下圖
字型的排版資訊
排版需要的變數有"glyph width"、"glyph height"、"code width"、"code height"、"code offset x"與"code offset y",接著來看怎麼從Freetype取出這些數值。

  "glyph width"的部分可以在"face->glyph->matrics.horiAdvance"取得,取得後記得除以64,就可以取得pixel的寬。

  "glyph height"比較簡單,還記得"FT_Set_Pixel_sizes()"嗎?最後一個參數就是這個數值。

  "code width"可以在"face->glyph->matrics.width"取得,取得後記得除以64。

  "code width"可以在"face->glyph->matrics.height"取得,取得後記得除以64。

  "code offset x"可以在"face->glyph->matrics.horiBearingX"取得,取得後記得除以64。

  "code offset y"這個比較麻煩,"glyph height-(face->glyph->bitmap_top+glyph height/4)",這計算式看起很奇怪是因為FreeType是左下座標為基準,但我定義的這個數值卻是左上座標,所以要做個換算。

  接著來說一些注意事項,有些字是無法讀出字型資訊的,比如說"\n",也就是換行,在畫字串時要記得處理特殊字元。"code offset x"與"code offset y"是有"負值"的,如"j"這個字元的"code offset x"就是負的。如果"code offset x"是負值,該值就不會被算在 "glyph width"裡面,如下圖
"code offset x"是負值的狀況

參考資料

Glyph Metrics

相關文章

初探FreeType

2019年3月18日 星期一

初探FreeType

初探FreeType

前言

  繪圖API並不支援畫字型的功能,以前使用Direct2D搭配DirectWrite來畫字型,但不幸地現在繪圖引擎要跨平台,Direct2D的方法最多只能在Windows平台使用,FreeType就是我找到的可以跨平台的畫字型方法,這裡把學習心得做個紀錄。

內容

  首先先來說說"建置",FreeType自帶的建置個人覺得實在是不怎麼好用,Windows版的建置是由官方給個Visual studio專案直接開啟後建置即可,但Linux的建置就完全不一樣,是類似透過Shell script的方式來建置,Android則是透過NDK的專案來建置,最後我並沒選擇官方的建置是因為FreeType在下載時是在Windows平台,所以裡面的文字檔的換行是"\r\n",結果當我在Ubuntu建置時都會報錯,也就是說跟建置相關的檔案絕不能在Windows的環境編輯,不然上傳後就沒辦法用了。解決的方案很簡單,就是自己建置,還記得官方有自帶一個Visual studio專案嗎?我依據該專案的建置方案來製作Linux與Android的建置,這方法有風險,但我還是做了,幸運的是Linux與Android的建置與Windows是差不多,差別的地方是Linux與Android版並沒有所謂的"Debug"版,編譯時不用加"FT_DEBUG_LEVEL_ERROR"與"FT_DEBUG_LEVEL_TRACE"編譯,其它的部分和Windows版是一樣的。

  接著來看看如何使用,先看以下範例
#include "ft2build.h"
#include FT_FREETYPE_H

int main()
{
  //...
  //
  FT_Library library;
  FT_Error error=FT_Init_FreeType(&library);
  if(error!=0)
    return 1;
  //
  //...
  //Free
  FT_Done_FreeType(library);

  return 0;
}

這個範例會去製造FT_Library,整個程式應該只要製造一個,在程式要結束時,透過FT_Done_FreeType()來釋放。FT_Library製造完後就可以來讀取font檔,範例如下
#include "ft2build.h"
#include FT_FREETYPE_H

int main()
{
  //...
  //
  FT_Library library;
  FT_Error error=FT_Init_FreeType(&library);
  if(error!=0)
    return 1;
  //Load font file
  FT_Face face;
  error=FT_New_Face(library,"arial.ttf",0,&face);
  //...
  //Free
  FT_Done_Face(face);
  FT_Done_FreeType(library);

  return 0;
}

讀取的方法很簡單,透過FT_New_Face()來讀取,透過FT_Done_Face()來釋放。在Android時,由於Android的Asset不提供Handle,如果要將Font檔放在Asset的話,就要透過FT_New_Memory_Face()來讀取。接下來就來讀取字型的資料,範例如下
#include "ft2build.h"
#include FT_FREETYPE_H

int main()
{
  //...
  //
  FT_Library library;
  FT_Error error=FT_Init_FreeType(&library);
  if(error!=0)
    return 1;
  //Load font file
  FT_Face face;
  error=FT_New_Face(library,"arial.ttf",0,&face);
  //Set font size
  error=FT_Set_Pixel_Sizes(face,0,60);
  //Load glyph
  FT_UInt glyphIndex=FT_Get_Char_Index(face,'a');
  if(glyphIndex==0)
    return 1;
  error=FT_Load_Glyph(face,glyphIndex,FT_LOAD_DEFAULT);
  if(face->glyph->format!=FT_GLYPH_FORMAT_BITMAP)
  {
    error=FT_Render_Glyph(face->glyph,FT_RENDER_MODE_MONO);
    if(error!=0)
      return 1;
  }
  //Get glyph bitmap data from face->glyph->bitmap.buffer
  //...
  //Free
  FT_Done_Face(face);
  FT_Done_FreeType(library);

  return 0;
}

 透過FT_Set_Pixel_Sizes()來設定字型的大小,接著透過Glyph index來寫資料到"buffer"裡,"buffer"在哪呢?透過face->glyph->bitmap.buffer來取得。

  Buffer的資料就是一張圖,整個Buffer的資料的大小為face->glyph->bitmap.pitch*face->glyph->bitmap.rows,"face->glyph->bitmap.rows"就是高,但"face->glyph->bitmap.pitch"並不是寬,每個Pitch指的是把寬度以每8個Pixel存成一個Byte來計算的話會有幾個Byte,如果寬度是60Pixel,Pitch值為8,如果寬度是40Pixel,Pitch值為5,依此類推。

參考資料

FreeType官網
Text Rendering with Direct2D and DirectWrite

2019年3月11日 星期一

箭頭函式的注意事項

箭頭函式的注意事項

前言

  ECMAScript6新增了箭頭函示語法,在網路上找到這篇ECMAScript 6 入门,看起來是個方便的語法,至少程式碼看起來比較簡短。在實際使用後發生了問題,在此做個紀錄。

內容

  箭頭函式剛用起來都沒什麼問題,但在使用到和'this'有關的時候可能會發生問題,範例如下
function CA(){
  this.value = 1234;
  this.myCall = function(callBackFun){
    callBackFun.call(this);
    
  };
  this.arrowCall = ()=>{
    console.log(this.value);
  };
}
let a = new CA();
//normal funciton call
a.myCall( function(){
  //output:1234
  console.log(this.value);
  
});
//arrow function call
a.myCall( ()=>{
  //output:undefined
  console.log(this.value);
  
} );
//output:1234
a.arrowCall();

這個範例在使用箭頭函式傳入"myCall"後會發現無法正常存取"value",如果把當下的"this"應出來會發現是"global",再來看看"arrowCall",在建構時使用箭頭函式,結果是正確的,this可以正常存取"value"。箭頭函式的"this"是建構當下的物件,無法透過call()來賦予"this",詳細的規則可以參考箭頭函式。如果覺得麻煩的話就在使用時想想這個函式會不會使用到"this",如果會就用舊的方式,如果不會就使用箭頭函式(看起來比較簡短)。

參考資料

ECMAScript 6 入门
箭頭函式

2019年3月4日 星期一

在Atom裡關閉預設外掛

在Atom裡關閉預設外掛

前言

  最近開啟Atom時會發生卡死的情況,畫面會停留上次開啟的檔案,但ㄏ不會有對任何輸入有反應,一陣子後會出現"Editor is not responding"的視窗,選擇"Force close"會直接關閉Atom,選另一個就在等一陣子又跳"Editor is not responding"視窗,這個情形要怎麼解決呢?在此把過程作個紀錄。

內容

  遇到這個問題馬上就google到這篇‘restore to default settings’ setting,這篇會說Atom的資料夾在"~/.atom",所有的設定包含外掛都會儲存在該資料夾,只要將該資料夾刪除後再開啟Atom就可以正常開啟,但不幸地的是所有的外掛與設定與開啟的專案都會消失,實在不是個好方法。

  到底是什麼樣的資料造成這個問題呢?答案是"外掛",外掛安裝的資料夾在"~/.atom/packages",所以我就一個一個刪掉來實驗室哪一個外掛,最後找到兇手
"linter-eslint",只要刪掉就可以正常開啟Atom了,只需重裝"linter-eslint"即可,比整個重裝好多了。但這樣還是不夠的,因為"linter-eslint"的安裝時間不算短,既然只有在開啟時會有問題,所以只要在開啟時不啟動該外掛不就可以了,接著我注意到這個檔案"~/.atom/config.cson",打開後可以看到以下
"*":
  core:
    disabledPackages: [
      "wrap-guide"
      "bracket-matcher"
      "welcome"
    ]
  "exception-reporting":
    userId: "a8e98b83-80d4-4e7b-aa3f-f3465fae1a95"
  "linter-ui-default": {}
  welcome:
    showOnStartup: false

注意"disabledPackages",我觀察了一下內容,都是我disable的外掛,接著我以實驗的精神在內容填上"linter-eslint",如下
"*":
  core:
    disabledPackages: [
      "wrap-guide"
      "bracket-matcher"
      "welcome"
      "linter-eslint"
    ]
  "exception-reporting":
    userId: "a8e98b83-80d4-4e7b-aa3f-f3465fae1a95"
  "linter-ui-default": {}
  welcome:
    showOnStartup: false

就這樣"linter-eslint"如我所願在開啟時不會被啟動,Atom也順利開啟。

參考資料

‘restore to default settings’ setting