分类目录归档:DirectUI

在cairo中使用pango文字渲染的文本显示区域是如何计算的

首先,pango可以使用pango_layout_get_pixel_extents获取ink_rect和logical_rect。

// API声明
void pango_layout_get_extents (PangoLayout    *layout,
			  PangoRectangle *ink_rect,
			  PangoRectangle *logical_rect);
// 调用示例
PangoRectangle rcInk, rcLogic;
pango_layout_get_pixel_extents(layout, &rcInk, &rcLogic);

假设使用cairo在(0, 0)位置输出文本”选项1″三个字,使用以上代码获取的 rcInk值为(x=0, y=6, w=52, h=19),rcLogi的值为(0,0,51,30)。那么输出的文字几个关键位置如下:

在绘制时,rcInk.y的值是实际墨迹的y方向起始值,可以理解为文字的顶部空白部分。这个空白在英文字母的绘制中比较明显,比如a会比i的rcInk.y的值更大,因为在四线三格中

垂直居中对齐:上图的是一个文本框的示意图,如果让“墨迹区域”在文本框box内垂直居中显示,可以按如下代码计算oy的值。

rcInk, rcLogic; 
...

topspace = rcInk.y;
oy = (box.h - ink.h) / 2 - topspace;

...
cairo_move(x, oy);
....

底部对齐:底部对齐可按如下方法 计算oy的值

rcInk, rcLogic; 
...

topspace = rcInk.y;
oy = box.h - ink.h - topsace;

...
cairo_move(x, oy);
....

关于windows系统的SetCapture

当我们需要精细化处理某个窗口的WM_MOUSEMOVE事件时,需要使用到SetCapture()。它使我们的程序可以在鼠标移出窗口区域、或在DISABLE的窗口中响应MouseMove事件。

正常情况下,我们会在WM_SETCURSOR事件中设置捕获。然后在鼠标移出窗口区域时释放捕获:

case WM_SETCURSOR:
    SetCapture(hWnd);
    break;

case WM_MOUSEMOVE:
    if (x < 0 || x > width)
        ReleaseCapture();

在Windows系统中,被 SetCapture的窗口在同一时刻只能有一个。并且当一个窗口SetCapture时,其它窗口无论是否处于上层,都将无法响应鼠标事件。所以当两个都需要设置Cature的窗口有重叠区域时,需要额外处理鼠标捕获。

如下图,窗口B在A的上层。当鼠标从起始点进入窗口A时,窗口A会首先SetCapture。但当鼠标向右移动到窗口B时,因为窗口A的Capture,窗口B是无法响应鼠标事件的。如果窗口A已经按上文描述在鼠标移出窗口A时释放捕获。当鼠标继续向右移出窗口A时,窗口B可开始捕获。

在实际应用中,可在MouseMove事件中即时检查鼠标坐标处的顶层窗口。如上图的情形,当鼠标“进入B”时,可在窗口A中释放捕获,交由窗口B继续处理。

字符集与编码方案详细说明

1 文字处理的最朴实场景 – 显示

以文字处理的朴素场景“显示”为例,我们反向推导一下怎么将某个文字在显示器上显示出来。

  • 对显示器来说,它并不知道自己显示的是什么。比如要显示文字“5”,应该给它文字“5”对应的位图数据;
  • 所以,我们首先得从一个地方得到“5”的位图;
  • 可以理解,“5”的位图不是凭空产生的,它必须是预先定义好的一个图片、或一组曲线……其它可显示的文字也应该有各自的绘图数据,这个存储着很多字符的绘图数据的东西,可能在很久以前是一个叫 “汉卡”的硬件、也可能在现在是一个“字体“文件;
  • 如果汉卡或某字体文件中定义了6万个字符,应该怎么找到文字“5”对应的绘图数据呢?
  • 这里出现了一个查询需求。无论是汉卡还是字体文件,它内部必须有一套索引体系,以便调用者查询到某个文字的绘图数据;这套索引实际是一套键值对集合,其中“键”对应要找的字符,“值”对应绘图数据;

到这个地方,整个逻辑就清晰了:

Q:一个信息化系统中,可以显示哪些文字?

A:本系统支持的字符集

Q:在汉卡或字体文件中查询文字的“键”是什么呢?

A:依据本系统中的文字编码方案

Q:各个信息化系统对同一文字,比如大家对“5”的编码方案相同吗?

A:各不相同。但未来的趋势是走向相同

2 字符集

2.1 字符

字符是人类可以理解的最小语言单位,泛指自然语言里所有的文字或符号。在电脑及电信(下文统称信息化)领域,字符是一个信息单位。它大约对应自然语言里的一个单位、符号,或是一个汉字、假名、韩文字,或是一个英文、一个其他西方语言的字母。或是一个音节里的一个音位、类音位……

此外,在信息化领域,还有一个“控制字符”的概念。控制字符并不对应到自然语言中的某个特定符号,而是对应到语言中用来处理文句的概念(类似排版)。如控制打印机的“换行符”、“Tab符”。

所以,我们讲的字符,可分为“可打印字符”和“控制字符”两类。

2.2 字符集

因为字符是人类的主观理解,所以其范围过度宽泛。在电脑及电信领域进行信息化处理时,任何一个子系统需要处理的字符范围都只能是一个有限集。所以标准化组织(国际或国家标准)或国家级行政单位会针对 “需要支持哪些字符”而设定一些规范。这些规定内“字符”的集合就叫字符集。

需要强调的是,字符集规范发布时会为每个字符都指定一个编号,这个编号的本质是一种排序。此编号数值与实际的字符编码数值并不完全一致。虽然大多数信息化系统中ASCII字符的编号正好是编码值。

2.3 编码方案

用什么数值来表示某个字符,是一种规范约定,这种约定即是“编码方案”。一个完整的编码方案,包含两个必须因素:①单个字符的的数据长度;②编码数值与文字的转换方法,这个转换方法可能包含一个编码转换表(代码页)。

2.3.1 早期的代码页方案

由于固定长度数据类型的取值范围是固定的,任何编码方案都是在用有限的整数取值范围去对应不同的字符集。为了支持更多的世界语言, IBM和微软都使用了代码页的概念,在不同语言的系统中使用不同的代码页处理文字。

对于欧洲和西亚语系,可以使用8位整数去对应他们的字符集。一般使用低7位的128个编码空间对应ASCII字符。然后,利用高8位去对应语言内的其它字符。如Windows简体中文系统中使用代码页CP936,繁体中文系统中使用代码页CP950。所以代码页是个字符映射表,早期的Windows系统会带着一个“字符映射表”程序。

各种代码页上的字节流含义是互不相同的。并且,IBM(OEM/IBM PC)的代码页与微软(ANSI)的代码页,即便语言相同,也是互不通用的。一个OEM上的字节流如果想正确的显示在Windows系统中,需要显示指定OME代码页。或显示转换OEM字节流为ANSI字节流,这时需要用到Windows系统的API: OemToChar()。

2.3.2 包含一切的万国码

代码页处理字符的局限显而易见,相同的数据使用不同的代码时,获得了不一样的字符。比如0xB9F1在简体中文CP936里是“国”,在CP950繁体中文里是“弊”。

所以,业界需要一个统一的“字符定义码”去包含世界上所有的字符,这样就可以在同一系统内支持世界语言。

这便是Unicode码,也被称为万国码、单一码、统一码。它是一个包含所有语言、所有字符的排序表,任意一个字符都有一个与之对应的排序号,这个排序号即是字符的Unicode码。Unicode码是一个连续的整数,目前已定义了0x00000-0xfffff区间的用途。①Unicode每隔0xffff区间,也叫1个平面,总共16个平面。

Unicode已被ISO作为国际标准采纳于通用字符集,即 ISO/IEC 10646,Unicode兼容ISO/IEC 10646且完整对应各个版本标准。

3 常见的国家标准字符集

3.1 GB2312

GB/T 2312[注 1],GB/T 2312–80 或 GB/T 2312–1980 是中华人民共和国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,通常简称GB(“国标”汉语拼音首字母),又称GB0,由中国国家标准总局于1980年发布,1981年5月1日实施。GB/T 2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB/T 2312。

GB/T 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。

3.2 BIG5

大五码(英语:Big5,又称为五大码)是使用繁体中文(正体中文)社群中最常用的电脑汉字字符集标准,共收录13,060个汉字。

“大五码”(Big5)是由台湾财团法人信息产业策进会为五大中文套装软件所设计的中文共通内码,在1983年12月完成公告,隔年3月,信息产业策进会与台湾13家厂商签定“16位个人电脑套装软件合作开发(BIG-5)项目(五大中文套装软件)”,因为此中文内码是为台湾自行制作开发之“五大中文套装软件”所设计的,所以就称为Big5中文内码。五大中文套装软件虽然并没有如预期的取代国外的套装软件,但随着采用Big5码的国乔中文系统及倚天中文系统先后在台湾市场获得成功,使得Big5码深远地影响繁体中文电脑内码,直至今日。“五大码”的英文名称“Big5”后来被人按英文字序译回中文,以致现在有“五大码”和“大五码”两个中文名称。

3.3 GB13000

GB 13000,中华人民共和国国家标准的国家标准代码之一,全称 GB 13000.1-93《信息技术 通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》。此标准等同采用国际标准化组织 ISO/IEC 10646.1:1993《信息技术 通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》。即“GB 13000.1-93”等同于Unicode 1.1版本。

GB 13000.1-93的字符集包含20,902个汉字,附录是GBK。

2010年1月10日发布的GB 13000-2010取代了GB 13000.1-93,于2010年11月1日实施。是ISO/IEC 10646:2003《信息技术 通用多八位编码字符集(UCS)》等同采用。由中国标准出版社出版,16开1476页。

3.4 GBK

全名为《汉字内码扩展规范(GBK)》1.0版,由中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司和电子工业部科技与质量监督司1995年12月15日联合以《技术标函[1995]229号》文件的形式公布。

GBK的K为“扩展”的汉语拼音(kuòzhǎn)第一个声母。

GBK共收录21886个汉字和图形符号,其中汉字(包括部首和构件)21003个,图形符号883个。

GBK 只为“技术规范指导性文件”,不属于国家标准。国家质量技术监督局于2000年3月17日推出了GB 18030-2000标准,以取代GBK。GB 18030-2000除保留全部GBK编码汉字,在第二字节把能使用范围再度进行扩展,增加了大约一百个汉字及四字节编码空间,但是将GBK作为子集全部保留。

3.5 GB18030

GB 18030,全称《信息技术 中文编码字符集》,是中华人民共和国国家标准所规定的变长多字节字符集。其对GB 2312-1980完全向后兼容,与GBK基本向后兼容,并支持Unicode(GB 13000)的所有码位。GB 18030共收录汉字70,244个。

GB 18030不是一个汉字规范,不定义汉字的写法。中国大陆在此方面的规定由《通用规范汉字表》管理。

3.6 通用规范汉字表

《通用规范汉字表》是《中华人民共和国国家通用语言文字法》的配套规范,是现代记录汉语的通用规范字集,体现着现代通用汉字在字量、字级和字形等方面的规范。2013年6月5日,国务院发出关于公布《通用规范汉字表》的通知,国务院同意教育部、国家语言文字工作委员会组织制定的《通用规范汉字表》,并予公布。《通用规范汉字表》公布后,社会一般应用领域的汉字使用以《通用规范汉字表》为准,原有相关字表停止使用。 该表共收录汉字8105个。

3.7 CJK中日韩统一表意文字

3.7.1 简介

中日韩统一表意文字(英语:CJK Unified Ideographs),也称统一汉字、统汉码(英语:Unihan),目的是要把分别来自中文、日文、韩文、越南文、壮文、琉球文中起源相同、本义相同、形状一样或稍异的表意文字,在ISO 10646及Unicode标准赋予相同编码。

所谓“起源相同、本义相同”、主要是汉字,包括繁体字、简化字、日本汉字(漢字/かんじ)、韩国汉字(漢字/한자)、琉球汉字(漢字/ハンジ)、越南的喃字(𡨸喃/Chữ Nôm)与儒字(𡨸儒/Chữ Nho)、方块壮字(𭨡倱/sawgun)。

3.7.2 历史版本

 Unicode码位版本及数量 
1993.5U+4E00–U+9FFF1.0 20902“〇”(码位U+3007),被当成数字放入了符号和标点区
1999U+3400–U+4DFFExt-A 6582 
2001U+20000–U+2A6FFExt-B 42,711 
2005U+9FA6–U+9FBBExt-B-修订1 22 
2009U+2A700–U+2B734Ext-C 4149 
2010U+2B740–U+2B81FExt-D 224  2012增加1个汉字:U+9FCC
2015U+2B820–U+2CEAF U+9FCD–U+9FD5Ext-E 5776 
2017U+2CEB0–U+2EBEF U+9FD6–U+9FEAExt-F 7488+142018增加5个汉字U+9FEB–U+9FE
  2020U+30000–U+3134A U+9FF0–U+9FFC U+4DB6–U+4DBF U+2A6D7–U+2A6DDExt-G 4939+13+10+6
U+31400–U+33D1F预计放置小篆,U+33E00–U+355FF预计放置甲骨文,相关提案已经提交。按路线图,该平面还会收录金文、简帛文、陶文、鸟虫书等

4 软件开发中的一些问题

4.1 如何读取一个文本文件

读取文本文件的过程是一个将字节流转换为字符串的解码过程。所以必须约定好文本文件中的字符串用什么格式存储。

在Windows系统中,一般使用BOM来指定文本文件的编码方式。即在文本文件的最开头使用一组特定的BOM字节流来指定本文件的文本编码方式。常见的BOM有以下几种。

编码类型BOM(十六进制)BOM (十进制)
UTF-8EF BB BF239 187 191
UTF-16 (BE)FE FF254 255
UTF-16 (LE)FF FE255 254
UTF-32 (BE)00 00 FE FF0 0 254 255
UTF-32 (LE)FF FE 00 00255 254 0 0
UTF-72B 2F 7643 47 118
UTF-1F7 64 4C247 100 76
UTF-EBCDICDD 73 66 73221 115 102 115
SCSU0E FE FF14 254 255
BOCU-1FB EE 28251 238 40
GB-1803084 31 95 33132 49 149 51

在Linux、Apple等系统中,一般不使用BOM,在存储时默认以UTF8存储。读取文本文件时,默认以UTF8方式识别。如果UTF8识别报错,会再尝试以本地语言代码页识别文本。

4.2 源代码中的字符串

在VisualStudio系列软件中,早期的代码文件默认以ANSI方式编码。后来默认以UTF16-LE BOM编码。所以读写源代码文件时,按如下优先级一次识别①识别BOM→②识别为本地代码页。

在跨平台开发中,如果源代码保存为UTF8编码且源文件中包含非ASCII字符时,VisualStudio编译会报无法识别的字符错误。要解决这个问题,可以使用/source-charset:utf-8指定源代码文件的默认编码响应方式。

同样,在GCC系列软件中,默认以UTF8方式编码。早期的GCC可能无法识别BOM头,导致编译报错无法识别的字符。

4.3 内存中的字符串

需要注意的是,VisualStudio开发环境中既区分了源代码的文件编码,又区分了字符串在内存中的编码。对于一个多字节字符串的双引号定义方式,如:const char* text = “大家好”;开发者需要特别关注到此时text中的字符串编码是本地语言的代码页编码。即使文件使用UTF8保存,且指定了/source-charset:utf-8。

也就是说,VisualStudio总是默认以本地语言代码页处理文本。而在GCC环境中,则总是以UTF8方式处理。

使用pango在windows中渲染文字的一些细节(2)

上篇文章使用pango在windows中渲染文字的一些细节(1)大致描述了pangov1.45.3中存在的的稍嫌不足之处,本文尝试去解决这些问题。

一、为pango提供SurrogateFallback(替换性备选字体)支持

1、第一步,实现windows系统读取SurrogateFallback的代码

read_windows_surrogate_fallback是在pango的read_windows_fallbacks()中使用,实现时直接传入line_buffer变量,并将当前字体的“替换性备选字体”追加进line_buffer变量中。

static void
read_windows_surrogate_fallback(GString* line_buffer, const wchar_t* fontname)
{
    HKEY hKey;
    LSTATUS status;

    wchar_t szPath[261];
    swprintf_s(szPath, 261, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\LanguagePack\\SurrogateFallback\\%s", fontname);
    status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, szPath, 0, KEY_READ, &hKey);
    if (status != ERROR_SUCCESS)
    {
        swprintf_s(szPath, 261, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\LanguagePack\\SurrogateFallback");
        status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, szPath, 0, KEY_READ, &hKey);
    }
    if (status != ERROR_SUCCESS)
        return;

    // Unicode当前规范包含从0x00000-0xfffff总共16个平面(panel)
    for (int i = 2; i < 16; i++)
    {
        wchar_t szPanel[16];
        DWORD dwSize;

        swprintf_s(szPanel, 16, L"Plane%d", i);

        dwSize = sizeof(szPath) / sizeof(szPath[0]);
        status = RegGetValueW(hKey, NULL, szPanel, RRF_RT_REG_SZ, NULL, szPath, &dwSize);
        if (status == ERROR_SUCCESS)
        {
            gchar* sutf8 = g_utf16_to_utf8(szPath, -1, NULL, NULL, NULL);
            g_string_append_printf(line_buffer, ",%s", sutf8);
            g_free(sutf8);
        }
    }

    RegCloseKey(hKey);
}

2、第二步,在pango的read_windows_fallbacks函数中增加SurrogateFallback支持

static void
read_windows_fallbacks (GHashTable *ht_aliases)
{
  ...
  for (value_index = 0; status != ERROR_NO_MORE_ITEMS; value_index++)
    {
      ...
      status = RegEnumValueW (hkey, value_index, name, &name_length,
                              NULL, NULL, NULL, NULL);
      ...
      utf8_name = g_utf16_to_utf8 (name, -1, NULL, NULL, NULL);
      ...
      g_string_append_printf (line_buffer,
                              "\"%s\" = \"%s",
                              utf8_name,
                              utf8_name);
      ...
      // 优先加入SurrogateFallback(替换性备选字体)
      read_windows_surrogate_fallback(line_buffer, name);

3、显示效果对比

下图是在windows11(10.0.22000.184)中选用MingLiU字体的显示效果对比。

第一行的pango渲染没有使用SurrogateFallback(替换性备选字体)支持,可以看见数字、英文、及汉字𩽾的显示均与MingLiU字体整体风格不符,尤其是汉字𩽾。

使用pango在windows中渲染文字的一些细节(1)

书写此文的背景

2021年9月使用cairo实现DirectUI时,发现有些特殊文字无法正常显示。

分析cairo及pango的代码后,发现pango虽然已通过fontmap方式解决了大部分文字渲染问题,但在处理Unicode非0平面(Plane)的字符时,仍存在不合理之处。

文字渲染的问题

1、严重错误:某些字符被渲染成“其它字符”比如将”𩽾29F7E“渲染成“齾9f7e“。这是cairo的一个BUG,不具普遍性。

2、无法显示:某些字符被渲染成一个“方块”或“数字码”,这是普遍的文字渲染问题,存在于很多软件中。Windows系统已经处理的很好,而且有完善的编程接口。可以参考:关于Windows TextOut输出文字时的一些难看的“方块”

3、显示不好:已设置为字体A,但部分字符被渲染为其它字体。比如经常看到某些软件或网页中的“少部分”文字的显示效果与其它文字不一样。这也是一个普通性问题。

cairo及pango的现状

一、cairo(v1.16)获取文字glyph索引的方式是完全错误的

没有考虑4字节(2个wchar_t)文字的情况,代码片段如下:

    wchar_t unicode[2];
    ...
    unicode[0] = ucs4;
    unicode[1] = 0;
    if (GetGlyphIndicesW (hdc, unicode, 1, &glyph_index, 0) == GDI_ERROR) {
	...
	glyph_index = 0;
    }
  • cairo官方不建议使用其自身的cairo_show_text渲染文字,而是使用pango。

二、pango(v1.45.3)已通过fontmap解决了“无法显示”的问题

指定字体“无法显示”某个字符的根本原因是:单个字体文件的可包含字符数(2^16)远小于Unicode字符总数(16*2&^16)。当待渲染字符没有被当前字体包含时,将产生“无法显示”问题。

所以在pango中使用fontmap将多个字体关联在一起, 只要这些关联字体的任意一个包含待渲染文字的glyph定义,即可完成渲染。

例如:SimSun(宋体)可以关联MINGLIU(细明)、MSYH(微软雅黑)等字体,渲染字符时,如果宋体里找不到该字符,就再找细明体、微软雅黑体。使用此方式可以解决绝大部分的“无法显示”问题

pango的默认代码中包含了两种fontmap:

1、依据Windows的fontlink信息,fontlink的数据存储在如下注册表位置

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink

2、写在代码内的几个默认定义。开发者可以扩展此方式创建自己的fontmap,还算灵活

static const char * const builtin_aliases[] = {
  "courier = \"courier new\"",
  /* It sucks to use the same GulimChe, MS Gothic, Sylfaen, Kartika,
   * Latha, Mangal and Raavi fonts for all three of sans, serif and
   * mono, but it isn't like there would be much choice. For most
   * non-Latin scripts that Windows includes any font at all for, it
   * has ony one. One solution is to install the free DejaVu fonts
   * that are popular on Linux. They are listed here first.
   */
  "sans = \"dejavu sans,tahoma,arial unicode ms,lucida sans unicode,browallia new,mingliu,simhei,gulimche,ms gothic,sylfaen,kartika,latha,mangal,raavi\"",
  "sans-serif = \"dejavu sans,tahoma,arial unicode ms,lucida sans unicode,browallia new,mingliu,simhei,gulimche,ms gothic,sylfaen,kartika,latha,mangal,raavi\"",
  "serif = \"dejavu serif,georgia,angsana new,mingliu,simsun,gulimche,ms gothic,sylfaen,kartika,latha,mangal,raavi\"",
 "mono = \"dejavu sans mono,courier new,lucida console,courier monothai,mingliu,simsun,SimSun-ExtB,gulimche,ms gothic,sylfaen,kartika,latha,mangal,raavi\"",
  "monospace = \"dejavu sans mono,courier new,lucida console,courier monothai,mingliu,simsun,gulimche,ms gothic,sylfaen,kartika,latha,mangal,raavi\"",
  "emoji = \"segoe ui emoji,segoe ui symbol,segoe ui\"",
  "cursive = \"commic sans ms\"",
  "fantasy = \"gabriola,impact\"",
  "system-ui = \"yu gothic ui,segoe ui,meiryo\"",
};

三、pango(v1.45.3)未考虑Windows的SurrogateFallback机制

Windows的SurrogateFallback机制可以指定Unicode每个平面(Plane)的字符渲染特定字体。这样的直接定位方式,可以避免过大的fontmap产生的效率问题。

例如,Win32系统的如下定义里,第一个”Plane2″=”SimSun-ExtB”指定所有无定义的字体在渲染Plane2字符时,均使用SimSun-ExtB渲染。第二个”Plane2″=”SimSun-ExtB”,特指SimSun字体在渲染Plane2字符时使用SimSun-ExtB渲染。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\LanguagePack\SurrogateFallback]
"Plane2"="SimSun-ExtB"

[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\LanguagePack\SurrogateFallback\SimSun]
"Plane2"="SimSun-ExtB"

四、pango(v1.45.3)未考虑Windows的Font Substitution

Windows的Font Substitution支持在渲染时将字体A替换为字体B,并且可以精确到具体字符集。也就是说,它支持将字体A中的字符集0替换为字体B中的字符集121进行渲染。这些设置保存在如下注册表中:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontSubstitutes