EBDT (TrueType 字体中的 Mono 嵌入式位图数据) 到 BM (位图)





5.00/5 (1投票)
随机读取TTF文件中的任何嵌入的单色矩阵,并将其导出为位图文件(打包在压缩文件中)
引言
此源代码展示了
- 如何从TTF(TrueType字体)文件中随机读取嵌入的单色汉字矩阵
- 如何将一些矩阵导出为位图文件(如果您只在GUI中看到它们,而其他人无法在其他平台上检查它们,那就太令人沮丧了。)
- 如何将它们全部导出到一个打包文件中(成千上万个微小文件=巨大的占用空间)
- (0.1)~(0.3)后,它们已检查通过,如何显示它们
- 如何从一个打包文件中制作一个TTF文件(仅单色EBDT,无矢量数据)
- 如何使用7z制作一个打包文件
在中低端嵌入式软件中,我们可能会使用几种汉字字体。但是,一个字体中有成千上万个小的矩阵(16*16?20*20?),导致文件碎片过多。如果我们将其打包到一个文件中,那么我们的美术设计师将花费大量时间在打包文件中查找(他们想要的)汉字矩阵。
我们的解决方案是将它们打包到一个TTF文件中。这样,在嵌入式软件中:没有文件碎片,并且我们可以像往常一样随机读取embedded_mono_matrix
。在PC上:我们的美术设计师可以使用TTF自由地文本输出(他们想要的)任何矩阵。
如果有人想修改(很少见,但并非不可能)其TTF文件中的某些矩阵,他们可以导出所有矩阵,修改其中一些,将它们打包到一个文件中,然后从中制作一个新的TTF文件。如果这种情况经常发生,请不要删除矩阵的位图,它可以供以后使用。
背景
Using the Code
1.1. ebdt2bm.c\get_matrix_fail(TTF_STRUCT* p_ttf, unsigned int scale_index, unsigned short glyph_index,..., unsigned char* dst_bmp, ...)
ttf文件以scale_index
和glyph_index
的规则顺序存储矩阵。例如
glyph_1 | glyph_2 | glyph_3 | glyph_4 | glyph_5 | glyph_6 | glyph_7 | glyph_8 | glyph_9 | ... | |
scale_0 (8*8) |
matrix_01 | matrix_02 | matrix_03 | matrix_04 | matrix_07 | matrix_08 | matrix_09 | ... | ||
scale_1 (16*16) |
matrix_13 | matrix_14 | matrix_15 | matrix_16 | matrix_17 | matrix_18 | ... | |||
scale_2 (20*20) |
matrix_22 | matrix_23 | matrix_25 | matrix_26 | matrix_28 | ... | ||||
... | ... |
---|
因此,"get_matrix_fail(&ttf,0,2,..,&bmp...)
"将获取bmp 中的matrix_02
;
"get_matrix_fail(&ttf,1,5,..,&bmp...)
"将获取 bmp 中的matrix_15
;
"get_matrix_fail(&ttf,2,7,..,&bmp...)
"将失败。
函数 "get_matrix_fail()
" 的剩余参数包括 "unsigned short *p_out_w"
和 "unsigned short *p_out_h"
。这对于导出位图可能很有用。
1.2. 如何将一些矩阵导出为位图文件
获取矩阵后,我们可以
1.2.1. 添加位图头(在 ebdt2bm.c 中#define
"USE_BMP
" 或 "USE_PBM
")。
1.2.2. 给它一个文件名。
1.2.3. 并保存位图文件。
ebdt2bm.c\int ebdt2bm_fail(TTF_STRUCT* p_ttf, unsigned int scale_index, unsigned short glyph_index,...)
是一个例子
unsigned short w,h;
unsigned int file_size;
if((ret=get_matrix_fail(p_ttf,scale_index,glyph_index,..,
&(bmp_buf[0x3e])..,&w,&h)!=0)return(ret);
// if we save in bmp format, we reserve (sizeof(BITMAPFILEHEADER) +
// sizeof(BITMAPINFOHEADER) + 2*sizeof(RGBQUAD))==0x3e bytes.
else if(make_bm_header_fail(-1,&(bmp_buf[0x3e]),w,h, &bmp_head,&file_size,NULL))return(11);
// (1.2.1)add BitMap header to it.
// if we save in bmp format, we fill its header, get the file_size of bmp;
// if we save in pbm format, we decrease header_size to 9 byte,
// reget the header, get the file_size of pbm.
...
else if(get_fileName_from_glyIdx_fail(p_ttf,wtr_bmp_name, glyph_index,w,h))return(12);
// (1.2.2)give it a file name.
// I use "wsprintfW(wtr_bmp_name,L"%04x_%02dx%02d(%c).pbm",unicode,w,h,unicode)" here,
// just to avoid duplication of file_names.
// Of course you can do anything here.
else if((ret=save_bmp_fail(wtr_bmp_name, bmp_head,file_size...))!=0)return(20+ret);
// (1.2.3)save the BitMap file, the details code are:
// hFile=fcreat_z(wtr_bmp_name,"wb"...);
// ret =fwrite_z(bmp_head,1,file_size,hFile);
// fclose_z(hFile);
else return(0);
GUI 在 1.5.3 中调用此函数。
1.3. 如何将所有矩阵导出到一个打包文件中
在 writeZip.c 中#define
"USE_ZIP
" 或 "USE_7Z
" 后,我们可以使用函数 zip_fcreat_fail()
和 zip_fclose()
。在这两者之间,我们遍历所有 gly_index
的 EBDT 矩阵并转换为位图。
ebdt2bm.c\export_zip(WCHAR* wtr_zip_name, TTF_STRUCT* p_ttf, unsigned int scale_index ...)
是一个例子
int res=0;
if(zip_fcreat_fail(wtr_zip_name,"wb",...))return(1);//constructor
else {for(show_seq=0; show_seq<max_show; show_seq++)
{glyph_index=g_ttf.get_glyIdx_from_seq[show_seq];
if(0xffff==glyph_index);//for above example: scale_2, glyph_7
else if(ebdt2bm_fail(p_ttf, scale_index,
glyph_index,...,&g_zip))//write sub_file in zip
{res=2; break;}
}
}
zip_fclose(&g_zip);//destructor
return(res);
GUI 在 1.5.3 中调用此函数。
1.4. ebdt2bm.c
从 1.1.~1.3. 是 ebdt2bm.c 的函数描述。它是一个单独的 C 文件(ebdt2bm.c)。在文件开头,有关于如何将其从 c 编译为控制台 EXE 的说明。EXE 的大小为 18K 字节。
用户也可以打开 vc6 项目(ebdt2bm.dsw)进行编译。
在 cmd 控制台中,输入 "ebdt2bm.exe /?
",我们将获得 str_default[] ={"ebdt2bm.exe <file.ttf> [font height]\r\n\t(default: font height=24)"}
示例 1:我们输入 "ebdt2bm.exe demo.ttf 25
",我们将获得 demo.7z [其中包含 0020_12x25.pbm, 0021_12x25.pbm, ..., ff0c_12x25(,).pbm]。
示例 2:我们输入 "ebdt2bm.exe demo.ttf
",我们将获得 demo.7z [其中包含 0020_12x24.pbm, 0021_12x24.pbm, ..., ff0c_12x24(,).pbm]。
示例 3:我们输入 "ebdt2bm.exe demo.ttf 20
",我们得到报告 "24x24,25x25, no scale match your font height"。 (24x24, 25x25,没有与您的字体高度匹配的缩放比例)
它导出了两个函数:fopen_ttf_fail()
和 fclose_ttf()
作为构造函数和析构函数。
int main(int argc,char** argv)
{
unsigned int font_height;
WCHAR wtr_zip_name[256];
WCHAR wtr_ttf_name[256];
TTF_STRUCT g_ttf;
unsigned int scale_index;
mem_set(&g_ttf,0,sizeof(TTF_STRUCT));
if((res=get_fontName_and_fontHeight(argc,argv,wtr_ttf_name,&font_height))!=0)
put_s(...);
else if(fopen_ttf_fail(wtr_ttf_name, font_height, &g_ttf,&scale_index ...))//constructor
put_s(...);
...
else if(get_zipName_from_ttfName_fail(wtr_zip_name, wtr_ttf_name));
else {res=export_zip(wtr_zip_name,&g_ttf,scale_index, ...);//see in (1.3)
if(0==res)
put_s(str_ok);
else
put_s(...);
}
fclose_ttf(&g_ttf);//destructor
}
GUI 在 1.5.3 中调用这些构造函数和析构函数。
1.5. charMap2.c
ebdt2bm.exe 只是一个控制台 EXE。 太可惜了! 我们可以给它一个 GUI 外壳, |
![]() |
charMap2.c 也是一个单独的 C 文件。在文件开头,有关于如何将其从 c 编译为 GUI EXE 的说明。EXE 的大小为 26K 字节。
用户也可以打开 vc6 项目(charMap2.dsw)进行编译。
1.5.1. 它的外观
它显示了 TTF 文件中的所有 embedded_mono_matrices
,它们按 scale_index
和 glyph_index
的规则顺序存储。所有字体缩放比例都显示在右上角的组合框中。所有 glyph_index
都在状态栏中显示为 "g=%04xh
"。每个矩阵都有其 matrix_offset
(在 TTF 文件中)、matrix_length_byte_cnt
、matrix_width(pixels)
和 matrix_height(pixels)
,因此它们显示在状态栏中,如 "ofs=%xh,len=%xh
" 和 "w=%d,h=%d
"。
但是 glyph_index
在 TTF 文件内部使用。在人类世界中,我们使用 Unicode,所以我们在状态栏中显示 printf("U+%04xh(%c)",ucs16,ucs16)
。在上方的捕获图像中,我们可以看到焦点 = Unicode 'A',其 hex_unicode_value=41h
,其 glyph_index=21h
。
如果我们想通过 Unicode、hex_unicode_value
或 glyph_index
来更改焦点,我们可以在对话框底部的 edit_box
中设置其中之一,按 enter_key
或单击 edit_box
上方的 arrow_button
,以更新状态栏中的旧值。
如果我们只是浏览矩阵,我们可以使用 arrow_key
、pgUp
、pgDn
,或单击滚动条。适合浏览!
1.5.2. 我们是如何做到的?
init_instance()
调用 prepare_bmp_content()
,后者代码如下:str_bmp=malloc(...)
,然后
p_wh=p_ttf->str_whls;//Width,Height,Length_byte_cnt,offSet
for(show_seq=0; show_seq<max_show; show_seq++,p_wh++)
{...
x=...; y=...;
glyph_index=g_ttf.get_glyIdx_from_seq[show_seq];
if(0xffff==glyph_index);
else {get_matrix_fail(p_ttf, d1.cur_combox_index, (unsigned short)glyph_index,0,
str_bmp,bmp_byte_per_line,
x+1,y+1, x+(mtx_w+1), y+(mtx_h+1),//the area which designated by us
&ofs,&byteCnt,&fmt,&w,&h);
p_wh->w=w; p_wh->h=h; p_wh->fmt=fmt; p_wh->leng=byteCnt; p_wh->ofs=ofs;
}
}
此代码获取 str_bmp
区域中的每个矩阵(该区域由我们指定),并记录矩阵的任何信息(例如 Width
、Height
、Length_byte_cnt
、offSet
)在我们的数组中。这样我们就可以在窗口中显示任何矩阵,并在状态栏中显示其信息。
1.5.3. 它的菜单
在 charMap2.c\WinMain() 中
if(WM_COMMAND==g_msg.message){
if(ID_MENU_OPEN_FNT==g_msg.wParam)
get_open_name(); //GetOpenFileNameW()
//+ fopen_ttf_fail(new_name) +
// fclose_ttf(old_ttf);
//see (1.4)
else if(ID_MENU_SAVE_1_BM==g_msg.wParam)
get_save_name(); //GetSaveFileNameW() + ebdt2bm_fail() //see (1.2)
else if(ID_MENU_SAVE_ALL_BM==g_msg.wParam)
get_export_name(); //GetSaveFileNameW() + export_zip()
//see (1.3)
else{;}
}
即使我们不使用菜单,我们也可以使用快捷键(ctrl+O,ctrl+S)调用 get_open_name()
或 get_save_name()
。
1.6. 如何从一个打包文件中制作一个TTF文件?
使用 bm2ebdt.exe,其源 C 文件是单独的 C 文件(bm2ebdt.c)。在 C 文件开头,有关于如何将其从 c 编译为控制台 EXE 的说明。EXE 的大小为 16K 字节。
用户也可以打开 vc6 项目(bm2ebdt.dsw)进行编译。
我们可以在 readZip.c(bm2ebdt.c 的一个子文件)中#define
"USE_ZIP
" 或 "USE_7Z
",默认值是 "USE_7Z
"。
在 cmd 控制台中,输入 "bm2ebdt.exe /?
",我们将获得 str_default[] ={"bm2ebdt.exe <file.7z>"}
示例 4:我们输入 "bm2ebdt.exe demo.7z
",我们将获得 demo.ttf。
1.6.1. 记录所有位图的宽度、高度和矩阵
在 bm2ebdt.c\int find_every_file_from_folder_fail(char* folder_name,...)
if(find_1st_file_fail(folder_name, &hSeek,...);
else
do {...
hFile_tmp=f_open(psp.cFileName,...,"rb",...);
check_bm_head_fail(hFile_tmp,...,&w,&h...);
read_mtx_fail(hFile_tmp...);
...
f_close(hFile_tmp);
}while(find_nx_file_ok(&hSeek,&psp...)
1.6.2. 为这些矩阵添加TTF头
在 bm2ebdt.c\int write_ttf_fail(char* ttf_name...)
hFile=_lcreat(ttf_name,0);
_lwrite(hFile,(char*)str_ttf_file_head,SIZEOFFILEHEAD);/* write 12 bytes of head */
_lwrite(hFile,(char*)str_dir,MAX_DIR<<4));/* write 160 bytes of table_directory */
for(i=0;i<MAX_DIR;i++)
{switch(i)
{
case HCMAP: j=write_cmap(hFile); break;
case HHEAD: j=write_head(hFile); break;
case HNAME: j=write_name(hFile); break;
case HPOST: j=write_post(hFile); break;
case HOS2: j=write_os2 (hFile); break;
case HMAXP: j=write_maxp(hFile); break;
case HHHEA: j=write_hhea(hFile); break;
case HHMTX: j=write_hmtx(hFile); break;
case HLOCA: j=write_loca(hFile); break;
case HGLYF: j=write_glyf(hFile); break;
case HEBLC: j=write_eblc(hFile); break;
case HEBDT: j=write_ebdt(hFile); break;
}
if(j)//err
{...}
else{;}
}
_lclose(hFile);
上述 switch 语句的最后一行是 bm2ebdt.c\int write_ebdt(HFILE hFile)
,它写入矩阵的主体。
unsigned char strbuf[864];
for(j=0;j<(unsigned short)c_map.gly_cnt;j++)
{...
size=get_mtx(...,strbuf);
_lwrite(hFile,(char*)strbuf,size)
}
1.7. 如何使用7z制作一个打包文件
如果我们使用 7z 格式 |
![]() |
如果我们使用 zip 格式 |
![]() |
1.8. 杂项
1.8.1. 平滑屏幕字体的边缘
当然,在使用此字体(仅单色 EBDT,无矢量数据)之前,我们必须禁用 Windows 操作系统的 "平滑屏幕字体边缘" 功能,迫使操作系统使用此字体的 EBDT。
1.8.2. 重新计算字体大小
例如,我们用 24 点矩阵制作一个字体,那么 Windows 假设该字体可以用作 Windows 3.0 中的 LOGPIXELS==72_dpi
和 font_height=24_dot
。
如果我们的 LCD 的 LOGPIXELS=96_dpi
,那么我们可以通过 font_size=18 (在 ChooseFont() 中)
看到 EBDT,这意味着在我们的 LCD 上是 font_size="18/72"
英寸,因此 tagLOGFONT.lfHeight=18/72 英寸 * 96_DotPerInch =24_dot
。
如果我们的 LCD 的 LOGPIXELS=120_dpi
,那么我们可以通过 font_size=15 (在 ChooseFont() 中)
看到 EBDT,这意味着在我们的 LCD 上是 font_size="15/72"
英寸,因此 tagLOGFONT.lfHeight=15/72 英寸 * 120_DotPerInch=25_dot
。
这些 24_dot
和 25_dot
是 bm2ebdt.c 中的 "unsigned int h_96dpi,h_120dpi
",它们是 demo.ttf 中的 scale_0
和 scale_1
。
1.8.3. 在 DOS 中使用
ebdt2bm.com, charMap2.com 和 bm2ebdt.com 是用 tc2.0 编译的(在相应的 c 文件开头,有关于如何将它们从 c 编译为 xxx.com 的说明),它们可以在 [dosbox] 或 [vdos] 或 [带 xms 驱动程序的 dos] 中使用。
在 dosbox 中,我们使用 "memsize=63
" 来获得 63MB 的 xms,并使用 "cycles=max
" 和 "core=full
" 来获得全速。
在 vdos 中,我们使用 "XMEM = 63 XMS
" 来获得 63MB 的 xms,使用 "SCALE = 1
" 来禁止 vdos 缩放,并使用 "MOUSE = ON
" 来在 charMap2.com 中显示鼠标。
但是它们不能在 Windows 的 cmd 控制台中运行,因为它们直接访问扩展内存。
关注点
在中国有一个黑色幽默:
如果 [我们嵌入式产品的矩阵] 没有精确匹配 [国家标准(其缩写为 "GB")的矩阵],我们的产品可能(不确定,只是可能)被 NITS(中国国家信息技术标准化技术委员会)判定为 "不合格产品"。
但是,如果矩阵精确匹配它们,那么我们可能就会卷入关于它们的专利侵权问题。
这是一个进退两难的局面。NITS {故意忽视小公司;} 而 (公司 != 富有);
当公司变得富有后,它们必须每 4 年购买一次字体的版权。根据 oemfont.com,在 2005 年 1 月,字体的价格是 GB2312_font=$15'000/4_years,GB13000_font=$40'000/4_years,GB18030_font=$40'000/4_years。
历史
- 2020年3月3日:提交,可以使用 bc 5.2 和 tc 2.0 编译。
- 2016年8月:可以使用 vc6.0 和 armCC 编译。