65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年3月3日

CPOL

8分钟阅读

viewsIcon

7365

downloadIcon

137

随机读取TTF文件中的任何嵌入的单色矩阵,并将其导出为位图文件(打包在压缩文件中)

引言

此源代码展示了

  1. 如何从TTF(TrueType字体)文件中随机读取嵌入的单色汉字矩阵
  2. 如何将一些矩阵导出为位图文件(如果您只在GUI中看到它们,而其他人无法在其他平台上检查它们,那就太令人沮丧了。)
  3. 如何将它们全部导出到一个打包文件中(成千上万个微小文件=巨大的占用空间)
  4. (0.1)~(0.3)后,它们已检查通过,如何显示它们
  5. 如何从一个打包文件中制作一个TTF文件(仅单色EBDT,无矢量数据)
  6. 如何使用7z制作一个打包文件

在中低端嵌入式软件中,我们可能会使用几种汉字字体。但是,一个字体中有成千上万个小的矩阵(16*16?20*20?),导致文件碎片过多。如果我们将其打包到一个文件中,那么我们的美术设计师将花费大量时间在打包文件中查找(他们想要的)汉字矩阵。

我们的解决方案是将它们打包到一个TTF文件中。这样,在嵌入式软件中:没有文件碎片,并且我们可以像往常一样随机读取embedded_mono_matrix。在PC上:我们的美术设计师可以使用TTF自由地文本输出(他们想要的)任何矩阵。

如果有人想修改(很少见,但并非不可能)其TTF文件中的某些矩阵,他们可以导出所有矩阵,修改其中一些,将它们打包到一个文件中,然后从中制作一个新的TTF文件。如果这种情况经常发生,请不要删除矩阵的位图,它可以供以后使用。

背景

TrueType字体 https://developer.apple.com/fonts/TrueType-Reference-Manual/
“嵌入式位图数据表”

或“位图数据表”

https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt

https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bdat.html

Portable BitMap(便携位图) http://netpbm.sourceforge.net/doc/pbm.html
7z http://cpansearch.perl.org/src/BJOERN/Compress-Deflate7-0.9/7zip/DOC/7zFormat.txt

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_indexglyph_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 外壳,
其外观类似于
Microsoft 的charmap.exe
所以我们称之为charMap2.exe

charMap2.c 也是一个单独的 C 文件。在文件开头,有关于如何将其从 c 编译为 GUI EXE 的说明。EXE 的大小为 26K 字节。

用户也可以打开 vc6 项目(charMap2.dsw)进行编译。

1.5.1. 它的外观

它显示了 TTF 文件中的所有 embedded_mono_matrices,它们按 scale_indexglyph_index 的规则顺序存储。所有字体缩放比例都显示在右上角的组合框中。所有 glyph_index 都在状态栏中显示为 "g=%04xh"。每个矩阵都有其 matrix_offset(在 TTF 文件中)、matrix_length_byte_cntmatrix_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_valueglyph_index 来更改焦点,我们可以在对话框底部的 edit_box 中设置其中之一,按 enter_key 或单击 edit_box 上方的 arrow_button,以更新状态栏中的旧值。

如果我们只是浏览矩阵,我们可以使用 arrow_keypgUppgDn,或单击滚动条。适合浏览!

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 区域中的每个矩阵(该区域由我们指定),并记录矩阵的任何信息(例如 WidthHeightLength_byte_cntoffSet)在我们的数组中。这样我们就可以在窗口中显示任何矩阵,并在状态栏中显示其信息。

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.cbm2ebdt.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 格式
(在 writeZip.creadZip.c#define "USE_7Z"),我们可以选择
compression_level="Store",以及 parameters="hc=off tm=off"(禁用 Header_Compressing,禁用文件的 TiMestamps),以制作一个用于 (1.6) 的打包文件。

如果我们使用 zip 格式
(在
writeZip.creadZip.c#define "USE_ZIP"),
我们也可以选择 compression_level="Store",以及 parameters="cu"
(使用 UTF-8 作为文件名)
以制作一个用于 (1.6) 的打包文件。

1.8. 杂项

1.8.1. 平滑屏幕字体的边缘

当然,在使用此字体(仅单色 EBDT,无矢量数据)之前,我们必须禁用 Windows 操作系统的 "平滑屏幕字体边缘" 功能,迫使操作系统使用此字体的 EBDT。

1.8.2. 重新计算字体大小

例如,我们用 24 点矩阵制作一个字体,那么 Windows 假设该字体可以用作 Windows 3.0 中的 LOGPIXELS==72_dpifont_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_dot25_dotbm2ebdt.c 中的 "unsigned int h_96dpi,h_120dpi",它们是 demo.ttf 中的 scale_0scale_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 编译。
© . All rights reserved.