UEFI开发探索18 – 使用HII显示汉字3

(请保留->发布地址: http://yiiyee.cn/blog/author/luobing/ )

本篇博客的关键字:Font。

今天准备把UEFI另外一种字体Font的方方面面研究一下,仍旧是以代码移植为主,间以解决自己提的几个问题。

1 Text模式和Graphics模式

实验之前,我想谈一下Text模式和Graphics模式。UEFI spec中,对于Text模式和Graphics模式谈的比较少。我一直很疑惑,spec中的Text模式和Graphics模式是不是我所理解的。

最早的Text模式为25行80列,屏幕上可以显示2000个字符。对应显示屏幕上的每个字符,在存储器中由连续的两个字节表示,一个字节保存ASCII码,另一个字节保存字符的属性。

对于所有的显示适配器,Text模式下显示字符的原理都是一样的,所不同的是各种适配器的视频显示存储器(就是显存)的起始地址不同:对MDA,起始地址为B000:0000;对CGA、EGA、VGA,起始地址为B8000:0000。 我们现在用的显示器都是VGA传承而来,因此Text模式起始地址为B800:0000。

图1 显示存储区与显示位置的关系

不管是直接调用BIOS int 10h、DOS int 21h、或是使用C的库函数printf,最终都是向B800:0000处写数据。这是我理解的Text模式。

Graphics模式更为复杂,可以参考Vesa标准。图形模式的显存起始地址是A000:0000,在使用之前必须设置其显示模式。不同的显示模式对应不同的分辨率,不同的颜色数。

在之前的博客《Foxdisk11-无字库显示汉字2》中简单的描述图形模式的编程,我常用的是1024×768、256色的图形模式。

很明显,这种模式连显存位置都不同,操作方法也是完全不同。Text模式下显示中文,需要额外的支持。

很久以前有人做过汉卡(比如联想倪光南、方正王选和巨人史玉柱),可以从硬件层面上增加字符发生器,实现Text模式下显示中文。而有些软汉卡则是在Graphics模式下,实现虚拟的文本态,来进行中文输入的。

而在上一博客的实验中,UEFI shell应该是在Text模式下。程序在此状态下运行,却可以打印出中文字符。我很怀疑UEFI下的Text模式并不是早期所定义的Text模式,至少不是在B800段进行操作。

真相如何,只能等读通了UEFI的源码后再来看了。这个问题并不影响我后续的编程,放一放吧。

2 Print直接打印汉字

在上节博客的代码中,增加了一行测试代码:

Print((const CHAR16*)L” 您好UEFI SimpleFont,My name is 罗冰~Robin.\n”); 其运行效果如下:

图2 测试Print打印汉字

看来Print是直接使用了SimpleFont来显示的。从目前的参考资料来看,仔细读读MdkModulePkg中关于显示的部分,能了解很多相关的细节知识。

我很想有一种调试的方法,可以跟踪执行,类似windbg一样,有源代码的话,可以定位到执行函数。这样就能很容易跟踪各个函数怎么运行的,调用了哪些函数。

3 Font格式

回到Font格式的研究吧。 Font格式比SimpleFont更为复杂,不再限制点阵大小,增加了边距和步长。

图3 Font点阵信息结构(UEFI Spec 2.8 page1813)

基于Font点阵构建的Font包,也比SimpleFont包更为复杂。它提供了索引的机制,由Font包头和点阵块列表组成。在我看来,实际上提供一种空间换时间的方式,以连续性存储的特性,加快了寻找速度。

图4 Font包头(UEFI Spec 2.8 page1809)

Font点阵块(Glyph Block)列表由多个点阵块排列而成,每种点阵块都有不同含义。点阵列块的类型比较多,这里不一一列举了,可以参考UEFI spec page1810~page1819。

另外一个可以读读的代码是\MdeModulePkg\Universal\HiiDatabaseDxe\Font.c,其中函数FindGlyphBlock()展示了在Font包中如何定位对应的Unicode字符。

移植的例子仍旧来自《UEFI原理与编程》,p280和p281的两张图对理解程序很有帮助。

4 代码移植及编译运行

这次的移植稍微有点麻烦,大部分是因为我的编译环境和作者的编译环境不大一样。我的编译器要求所有的变量定义必须放在执行体前,应该是C89标准的原因,作者的代码无法编译。

另外,结构体的初始化也有问题,估计也与C89/C99有关。

我将FillGLYPH()、FillNarrowGLYPH()、FillWideGLYPH()以及CreateDMFontPkg()移植到Luo2.c中,同时将测试用的代码拷贝到main函数中。 发现有个外部变量extern CHAR16* FontName; 不知道藏在哪个文件中,怎么也找不到。将其改为局部变量,随便给了个值,编译通过。

图5 程序运行结果

文字很诡异的出现在了左上角,同时把后面的字符盖住了。这是以图形方式把字符串显示出来了,StringToImage的原理就是用Blt把字符串位图输出到屏幕上的。

这某种程度上证明了我的猜想,UEFI Shell的Text模式实际上是运行在某种Graphics模式下的,通过模拟的方式来运行shell,否则Blt是没法将位图输出的。至少UEFI Text模式不应该是传统认为的Text模式。

我想在UEFI Shell中使用Font的期望落空了,至少不是我想象中的模样。其实也对,Font字模大小比较自由,怎么能和Shell中的其他文字兼容呢?UEFI Shell中的文字显示,应该是调用SimpleFont字库的。

5 One more thing

看到UEFI Shell下显示的盘符,是黑底黄字的。我就琢磨,怎么才能实现这种效果?直接使用Print可以实现吗?

Legacy BIOS的处理比较简单,其核心原理是修改显存的属性字节。不管是BIOS中断还是DOS中断,最终都是这样处理的。

printf()也提供了对字体颜色和背景处理的方法,虽然语法比较诡异。

UEFI下的Print,和printf还是有些不同,用同样的方法去尝试,不成功。我也没有精力去追踪Print的实现。

最后发现ConOut提供了SetAttribute,可以用来修改文字的前景以及背景色(UEFI Spec 2.8 page449)。写了几句测试代码,效果如下:

图6 打印彩色文字

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
代码在 11 HiiShellPrint-Font 下。

3,360 total views, 1 views today

发表评论

电子邮件地址不会被公开。