2020年9月3日 星期四

使用 freetype2 (0) - 中文秀字原理

如果某個想法是你唯一的想法, 那就沒有比它更危險的東西了。
freetype2 是幹麻用的, 簡單來說就是可以把 type1, truetype 字型解譯出來, 讓我們取得字型內容, 然後用繪圖函式來秀出某一個字。如果沒有繪圖函式, 可以用 printf 來秀出這個字, 雖然粗糙了點, 但可以方便的測試。

freetype2 非常的複雜, 我在很久之前 (2001 左右) 讀了其內建的手冊, 異常辛苦, 印出來的紙本滿滿是我的注記。學習一個 library 尤其是一個複雜的 library, 沒有比給一個範例程式更好的學習方式了, 手冊雖然有範例, 但是不夠完整, 所以我才寫了這個, 可以編譯、執行、印出字型中的字, 而且只需要 c++17 就可以了, 不需要進入繪圖模式, 在 2019 年, 支援 c++17 的編譯器並不難找。

其中會需要 unicode 轉換, 我是借助 c++11 的標準程式庫來完成, c++11 來的即時, 更早之前, 我是用 qstring 來做 unicode 轉換, 為了 unicode 要 link qt 實在是有點蠢。

source code:
https://github.com/descent/progs/tree/master/cpp11_utf8

ft2_demo.cpp
  1 /* demo ft2 by printf */
  2 
  3 #include <unistd.h>
  4 
  5 #include <ft2build.h>
  6 #include FT_FREETYPE_H
  7 #include FT_GLYPH_H
  8 
  9 #include <vector>
 10 #include <fstream>
 11 #include <locale>
 12 #include <iostream>
 13 #include <iomanip>
 14 #include <string>
 15 
 16 #include <cstdio>
 17 #include <cstdlib>
 18 #include <cmath>
 19 
 20 #include <codecvt>
 21 #include <locale>
 22 
 23 using namespace std;
 24 
 25 // convert UTF-8 string to wstring
 26 static inline std::wstring utf8_to_wstring (const std::string& str)
 27 {
 28     std::wstring_convert<std::codecvt_utf8<wchar_t>> myconv;
 29     return myconv.from_bytes(str);
 30 }
 31 
 32 // convert wstring to UTF-8 string
 33 static inline std::string wstring_to_utf8 (const std::wstring& str)
 34 {
 35     std::wstring_convert<std::codecvt_utf8<wchar_t>> myconv;
 36     return myconv.to_bytes(str);
 37 }
 38 
 39 const char *c_fg = "*";
 40 const char *c_bg = "|";
 41 char aa = '0'; // anti-alias
 42 int font_size=10;
 43 
 44 void print_raw_data(FT_Bitmap *bitmap)
 45 {
 46   unsigned char *tmp = bitmap->buffer;
 47   for (int i=0 ; i < bitmap->rows ; i++)
 48   {
 49     unsigned char c = *tmp;
 50     
 51     for (int j=0 ; j < bitmap->pitch ; j++)
 52     {
 53       printf("%x, ", c); 
 54       ++tmp;
 55       c = *tmp;
 56     }
 57     printf("\n");
 58   }
 59 }
 60 
 61 void my_draw_bitmap_256(FT_Bitmap *bitmap,int pen_x,int pen_y)
 62 {
 63   cout << "bitmap rows : " << bitmap->rows << endl;
 64   cout << "bitmap width : " << bitmap->width << endl;
 65   cout << "bitmap pitch : " << bitmap->pitch << endl;
 66   if (bitmap->pixel_mode==ft_pixel_mode_mono)
 67     cout << "bitmap pixel mode : mono" << endl;
 68    if (bitmap->pixel_mode==ft_pixel_mode_grays)
 69    {
 70     cout << "bitmap pixel mode : grays" << endl;
 71     cout << "bitmap grays level : " << bitmap->num_grays << endl;
 72    }
 73 
 74 }
 75 
 76 void my_draw_bitmap_mono(FT_Bitmap *bitmap,int pen_x,int pen_y)
 77 {
 78   uint8_t r, g, b;
 79   cout << "bitmap rows : " << bitmap->rows << endl;
 80   cout << "bitmap width : " << bitmap->width << endl;
 81   cout << "bitmap pitch : " << bitmap->pitch << endl;
 82   if (bitmap->pixel_mode==ft_pixel_mode_mono)
 83     cout << "bitmap pixel mode : mono" << endl;
 84    if (bitmap->pixel_mode==ft_pixel_mode_grays)
 85    {
 86     cout << "bitmap pixel mode : grays" << endl;
 87     cout << "bitmap grays level : " << bitmap->num_grays << endl;
 88    }
 89 
 90   int startx = pen_x;
 91   int starty = pen_y;
 92   int cx=0, cy=0;
 93 
 94   unsigned char *tmp = bitmap->buffer;
 95   for (int i=0 ; i < bitmap->rows ; i++)
 96   {
 97     unsigned char c = *tmp;
 98     
 99     int font_w = 0;
100     for (int j=0 ; j < bitmap->pitch ; j++)
101     {
102       #if 0
103       printf("%x, ", ch); 
104       ++tmp;
105       ch = *tmp;
106       #endif
107 
108 
109 
110       for (int i=7 ; i>=0 ; --i)
111       {
112         if (font_w < bitmap->width)
113         {
114 
115         if (((c >> i) & 0x1) == 1)
116         {
117             printf(c_fg);
118         }
119         else
120         {
121             printf(c_bg);
122 
123         }
124 
125         }
126         //++cur_x;
127         ++cx;
128         ++font_w;
129       }
130       ++tmp;
131       c = *tmp;
132 
133     }
134     cx=0;
135     ++cy;
136     printf("\n");
137   }
138 }
139 
140 void usage(const char *fp)
141 {
142   printf("%s -p font_path -s font_size -t render_string -f fb -b bg -g 0 -a 0 -m [opened file] -x x -y y -d step_y\n", fp);
143 }
144 
145 int main(int argc, char *argv[])
146 {
147   const char *disp_str = "a中文bあい";
148   //string fontpath="./fireflysung.ttf";
149   //string fontpath="./unifont.pcf.gz";
150   string fontpath="./bsmi00lp.ttf";
151   int opt;
152 
153   string textline;
154   int x=0, y=100, step_x=16, step_y = 16;
155   while ((opt = getopt(argc, argv, "d:m:x:y:t:a:s:b:f:p:g:h?")) != -1)
156   {
157     switch (opt)
158     {
159       case 't':
160       {
161         disp_str = optarg;
162         break;
163       }
164       case 's':
165       {
166         font_size = strtol(optarg, 0, 10);
167         break;
168       }
169       case 'p':
170       {
171         fontpath = optarg;
172         cout << "fontpath: " << fontpath << endl;
173         break;
174       }
175       case 'f':
176       {
177         c_fg = optarg;
178         break;
179       }
180       case 'b':
181       {
182         c_bg = optarg;
183         break;
184       }
185       case 'a':
186       {
187         aa = optarg[0];
188         break;
189       }
190       case 'h':
191       {
192         usage(argv[0]);
193         return 0;
194       }
195     }
196   }
197 
198   FT_Library library;
199   FT_Face face;
200   FT_Error error;
201 
202   error=FT_Init_FreeType(&library);
203   if (error!=0)
204   {
205     cout << "FT_Init_FreeType(&library) error!!" << endl;
206     return -1;
207   }
208 
209   error=FT_New_Face(library,fontpath.c_str(),0,&face); // 從字型載入 face
210   if (error==FT_Err_Unknown_File_Format)
211   {
212     cout << "Don't support this font file" << endl;
213     return -1;
214   }
215   else if (error)
216        {
217          cout << "The font file cann't be opened!" << endl;
218          return -1;
219        }
220   cout << "face information : " << endl;
221   cout << "face number is : " << face->num_faces << endl;
222   cout << "face glyphs number is : " << face->num_glyphs << endl;
223   cout << "face's sytle name is : " << face->style_name << endl;
224   cout << "units per EM : " << face->units_per_EM << endl;
225   cout << "num_fixed_sizes : " << face->num_fixed_sizes << endl;
226   cout << "flags : " << face->face_flags << endl;
227   if (face->charmap==NULL)
228    cout << "No charmap is selected" << endl;
229   cout << "charmap numbers is : " << face->num_charmaps << endl;
230   error=FT_Select_Charmap(face,ft_encoding_unicode);
231   if (error)
232   {
233     cout << "FT_Select_CharMap(face,ft_encoding_unicode) error"  << endl;
234     return -1;
235   }
236 
237   FT_UInt gindex;
238 
239   //QVector<uint> utf32_str = str.toUcs4();
240   //QVector<uint> utf32_str = utf8_to_ucs4(disp_str);
241   //vector<wchar_t> utf32_str = utf8_to_ucs4(disp_str);
242 
243   std::wstring utf32_str = utf8_to_wstring(disp_str);
244 
245   cout << endl;
246 
247   for (int i=0 ; i < utf32_str.size() ; ++i)
248   {
249     cout << "utf-32: " << utf32_str[i] << endl;
250 
251     switch (utf32_str[i])
252     {
253       //case 0xd:
254       //case 0xa:
255       case 0x9: // tab
256       {
257         x += 16;
258         continue;
259         break;
260       }
261       case '\n':
262       {
263         x = 0;
264         y += step_y;
265         continue;
266         break;
267       }
268     }
269 
270     gindex = FT_Get_Char_Index(face, utf32_str[i]);
271     if (gindex==0)
272     {
273       cout << "glyph index not found" << endl;
274       continue;
275     }
276 
277     if (face->face_flags & FT_FACE_FLAG_SCALABLE)
278     {
279       #if 1
280       error = FT_Set_Pixel_Sizes(face, font_size, font_size);
281       if (error)
282       {
283         cout << "FT_Set_Pixel_Sizes error" << endl;
284         return -1;
285       }
286       #else
287       // only scale font can set font size.
288       error=FT_Set_Char_Size(face, 0, font_size*64,360,360);
289       if (error)
290       {
291         cout << "FT_Set_Pixel_Sizes error" << endl;
292         return -1;
293       }
294       #endif
295     }
296 
297   FT_Int load_flags=FT_LOAD_DEFAULT;
298   error = FT_Load_Glyph(face, gindex,load_flags);
299   if (error!=0)
300   {
301    cout << "FT_Load_Glyph(face,glyph_index,load_flags) is fail " << endl;
302    cout << "The error number is : " << error << endl;
303    return -1;
304   }
305   if (face->glyph->format!=ft_glyph_format_bitmap)
306   {
307    cout << "run FT_Render_Glyph" << endl;
308    //error=FT_Render_Glyph(face->glyph,ft_render_mode_normal);
309    if (aa == '1')
310      error=FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
311    else
312      error=FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO);
313    if (error)
314    {
315     cout << "FT_Render_Glyph error " << endl;
316     return -1;
317    }
318   }
319 
320     FT_GlyphSlot slot=face->glyph;
321     cout << "slot->bitmap_left: " << slot->bitmap_left << endl;
322     cout << "slot->bitmap_top: " << slot->bitmap_top << endl;
323     cout << "x: " << x << endl;
324     cout << "y: " << y << endl;
325     cout << "draw x: " << x + slot->bitmap_left << endl;
326     cout << "draw y: " << y - slot->bitmap_top << endl;
327 
328     // ref: fbterm-1.7/src/font.cpp Font::Glyph *Font::getGlyph(u32 unicode)
329     int left = face->glyph->metrics.horiBearingX >> 6;
330     int top = (face->size->metrics.descender >> 6) - (face->glyph->metrics.horiBearingY >> 6);
331     cout << "left: " << left << endl;
332     cout << "top: " << top << endl;
333 
334     if (aa=='1')
335     {
336       my_draw_bitmap_256(&slot->bitmap, x + slot->bitmap_left, y - slot->bitmap_top);
337     }
338     else
339     {
340       my_draw_bitmap_mono(&slot->bitmap, x + slot->bitmap_left, y - slot->bitmap_top);
341     }
342     print_raw_data(&slot->bitmap);
343     #if 1
344     x += slot->advance.x >> 6;
345     y += slot->advance.y >> 6;
346     #else
347     x += slot->advance.x;
348     y += slot->advance.y;
349     #endif
350   }
351 
352   FT_Done_FreeType(library);
353   return 0;
354 }

L198 ~ L320 就是在取出字型的 bitmap, 可以看到需要呼叫好幾個 api, 有了這個資料就可以在螢幕上畫出這個字。

unicode 點陣字型 (有日文)

apt-get install xfonts-unifont
/usr/share/fonts/X11/misc/unifont.pcf.gz

文鼎捐贈的字型 (只有繁體中文英文數字)
apt-get install fonts-arphic-bsmi00lp
/usr/share/fonts/truetype/arphic-bsmi00lp/bsmi00lp.ttf

只有畫出文字是不夠的, 還要畫在正確的位置, 這需要繪圖模式才辦得到, 以下的範例在於正確取得座標位置, 避免畫出來的 2 個水平文字無法水平對齊。這篇不會提到這個, 下篇再來說明這個。

https://www.freetype.org/freetype2/docs/tutorial/example1.c
參考
/* now, draw to our target surface (convert position) */
    draw_bitmap( &slot->bitmap,
                 slot->bitmap_left,
                 target_height - slot->bitmap_top );
畫出的座標

執行結果:
 1 |||*||||
 2 |||*||||
 3 ********
 4 *||*|||*
 5 ********
 6 *||*|||*
 7 |||*||||
 8 |||*||||
 9 ||||||||
10
11
12 ||||||||||
13 ||||*|||||
14 |********|
15 ||*|||*|||
16 |||*||*|||
17 |||*|*||||
18 ||||**||||
19 |||||*||||
20 |||**|*|||
21 |**||||***

可以隱約看出「中文」這 2 個字。

ref:
FreeType简易教程

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。