HII Font
接下来介绍EFI_HII_FONT_PROTOCOL
,它在UEFI代码中完成了字符到像素的转换,本节主要介绍这个转换关系,它的实现代码在edk2\MdeModulePkg\Universal\HiiDatabaseDxe\HiiDatabaseDxe.inf中,除了EFI_HII_FONT_PROTOCOL
,这个模块还实现了很多其它的Protocol,后面用到的时候也会介绍,所以HiiDatabaseDxe.inf这个模块并不仅仅是字体的基础,而是UEFI的人机接口(Human Interface Infrastructure)的基础模块。
结构体组织
下图是HII数据库基础中字体相关的结构体:
从图中可以看出,字体分为两种,一种是普通版本另一种是简化版本,本节将分别介绍。
最左边的结构体HII_DATABASE_PRIVATE_DATA
是HII数据库模块的基础结构体,对应字体部分:
HII_DATABASE_PRIVATE_DATA mPrivate = {
// 前略
LIST_ENTRY DatabaseList; // 所有HII资源都放在这个列表中,其中就包括字体和简单字体
// 前略
{
HiiStringToImage,
HiiStringIdToImage,
HiiGetGlyph,
HiiGetFontInfo
},
// 中略
LIST_ENTRY FontInfoList; // global font info list
// 后略
};
其中包含有一个Protocol,对应的是EFI_HII_FONT_PROTOCOL
,其接口:
///
/// The protocol provides the service to retrieve the font informations.
///
struct _EFI_HII_FONT_PROTOCOL {
EFI_HII_STRING_TO_IMAGE StringToImage;
EFI_HII_STRING_ID_TO_IMAGE StringIdToImage;
EFI_HII_GET_GLYPH GetGlyph;
EFI_HII_GET_FONT_INFO GetFontInfo;
};
这个将在后面介绍。
还包含一个数据库列表DatabaseList
,它包含了所有HII的资源,其中就包括注册了的字体部分内容。以及一个字体信息列表FontInfoList
,指向额外的结构体HII_GLOBAL_FONT_INFO
,它包含了普通字体需要使用到的数据,而简单字体并不需要这部分。因为是一个列表,所以表示UEFI可以支持多种字体信息。
HII_GLOBAL_FONT_INFO
结构体如下:
typedef struct _HII_GLOBAL_FONT_INFO {
UINTN Signature;
LIST_ENTRY Entry;
HII_FONT_PACKAGE_INSTANCE *FontPackage;
UINTN FontInfoSize;
EFI_FONT_INFO *FontInfo;
} HII_GLOBAL_FONT_INFO;
重要的是后面的三个成员,表示两类信息,FontPackage
和FontInfo
。其中包含的字体和字形的所有信息。关于字体(Font)和字形(Glyph)需要特别说明:
- 字体是所有字符的整体表现形式,比如楷体、宋体等,表示的是一个整体的概念。
- 字形是每个字符的样子,会真正涉及到UEFI下如何绘制一个字符。
- 所有的字形组成了一种字体样式。
下面对字体信息相关的结构体做简单的介绍。首先是描述字体本身的结构体:
typedef struct {
EFI_HII_FONT_STYLE FontStyle; ///< 一个枚举值,表示的是粗体、斜体等
UINT16 FontSize; ///< character cell height in pixels
CHAR16 FontName[1];///< 字体名,这个在文本编辑器里面更容易看到
} EFI_FONT_INFO;
当前支持的字体类型(对应FontStyle
):
//
// Value for font style
//
#define EFI_HII_FONT_STYLE_NORMAL 0x00000000
#define EFI_HII_FONT_STYLE_BOLD 0x00000001
#define EFI_HII_FONT_STYLE_ITALIC 0x00000002
#define EFI_HII_FONT_STYLE_EMBOSS 0x00010000
#define EFI_HII_FONT_STYLE_OUTLINE 0x00020000
#define EFI_HII_FONT_STYLE_SHADOW 0x00040000
#define EFI_HII_FONT_STYLE_UNDERLINE 0x00080000
#define EFI_HII_FONT_STYLE_DBL_UNDER 0x00100000
然后是单个字形的结构体:
typedef struct _HII_GLYPH_INFO {
UINTN Signature;
LIST_ENTRY Entry;
CHAR16 CharId;
EFI_HII_GLYPH_INFO Cell;
} HII_GLYPH_INFO;
这里有一个列表Entry
是因为字形本来就需要有很多个,CharId
表示的是字符编码值,通过一个CHAR16
理论上能够覆盖所有常用语言的字符,最后的Cell
结构体如下:
typedef struct _EFI_HII_GLYPH_INFO {
UINT16 Width;
UINT16 Height;
INT16 OffsetX;
INT16 OffsetY;
INT16 AdvanceX;
} EFI_HII_GLYPH_INFO;
这些值与字符的对应关系图如下所示:
剩下的结构体EFI_HII_FONT_PACKAGE_HDR
和HII_FONT_PACKAGE_INSTANCE
主要也是上述字体和字形结构体的组织形式的描述。
需要注意,到这里为止仅仅是描述了字体和字形相关的结构,并没有涉及到真实地描述从字符到EFI_GRAPHICS_OUTPUT_PROTOCOL
能够显示的图形之间的数据转换模型,这部分内容隐藏在前面的某个结构体成员中,主要是GlyphBlock
,作为结构体成员它只是一个指针,通过它我们可以找到某个字符对应的图像显示数据,所以对于它的设计是比较重要的,因为我们需要快速地找到字符对应的图像。
实际上由于使用的限制,字体和字形的应用在UEFI下并不是很重要,UEFI只要能够清楚地表示字符就可以了,为此就有了简化版本的字体,它的结构相当简单,重要的是下面的结构体:
///
/// A simplified font package consists of a font header
/// followed by a series of glyph structures.
///
typedef struct _EFI_HII_SIMPLE_FONT_PACKAGE_HDR {
EFI_HII_PACKAGE_HEADER Header;
UINT16 NumberOfNarrowGlyphs;
UINT16 NumberOfWideGlyphs;
// EFI_NARROW_GLYPH NarrowGlyphs[];
// EFI_WIDE_GLYPH WideGlyphs[];
} EFI_HII_SIMPLE_FONT_PACKAGE_HDR;
虽然结构体中包含了HDR的字样,但是从上面的代码中也可以看出来,它之后直接就接了所有的数据,通过这些数据可以直接将字符转换成可输出的图形。后续的数据包含两个部分,分别对应窄体字符和宽体字符,实际就是对应8x19和16x19两种形式,以窄体为例,一个字符对应到的数据如下:
typedef struct {
CHAR16 UnicodeWeight;
UINT8 Attributes;
UINT8 GlyphCol1[EFI_GLYPH_HEIGHT]; // EFI_GLYPH_HEIGHT = 19
} EFI_NARROW_GLYPH;
UnicodeWeight
对应到字符的计算机表示,Attributes
表示字符的属性,目前支持的值:
///
/// Contents of EFI_NARROW_GLYPH.Attributes.
///@{
#define EFI_GLYPH_NON_SPACING 0x01
#define EFI_GLYPH_WIDE 0x02
#define EFI_GLYPH_HEIGHT 19
#define EFI_GLYPH_WIDTH 8
///@}
GlyphCol1
实际上是一个位图,每一个比特位表示了是否需要绘制该像素。在【UEFI实战】UEFI图形显示(从像素到字符)的代码示例中,使用了一个二维数组来表示是否需要绘制对应像素:
UINT8 BltIndex[NARROW_HEIGHT * NARROW_WIDTH] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0,
0, 1, 1, 0, 1, 1, 0, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
显然这里的方式比上面的代码更加的简单和紧凑。
开源的EDK代码就是使用了这种简化的字体形式来表示字符。
字符到字形的表示
注册字符到字形的描述数据,涉及到HiiDataBase的一个接口:
///
/// Database manager for HII-related data structures.
///
struct _EFI_HII_DATABASE_PROTOCOL {
EFI_HII_DATABASE_NEW_PACK NewPackageList;
// 后略
};
这里不写它的函数原型,而是直接关注对于字体来说其实现中最重要的的部分,其调用流程如下:
字体注册数据的位置在前面介绍过的GraphicsConsoleDxe模块中:
//
// Add 4 bytes to the header for entire length for HiiAddPackages use only.
//
// +--------------------------------+ <-- Package
// | |
// | PackageLength(4 bytes) |
// | |
// |--------------------------------| <-- SimplifiedFont
// | |
// |EFI_HII_SIMPLE_FONT_PACKAGE_HDR |
// | |
// |--------------------------------| <-- Location
// | |
// | gUsStdNarrowGlyphData |
// | |
// +--------------------------------+
PackageLength = sizeof (EFI_HII_SIMPLE_FONT_PACKAGE_HDR) + mNarrowFontSize + 4;
Package = AllocateZeroPool (PackageLength);
ASSERT (Package != NULL);
WriteUnaligned32 ((UINT32 *)Package, PackageLength);
SimplifiedFont = (EFI_HII_SIMPLE_FONT_PACKAGE_HDR *)(Package + 4);
SimplifiedFont->Header.Length = (UINT32)(PackageLength - 4);
SimplifiedFont->Header.Type = EFI_HII_PACKAGE_SIMPLE_FONTS;
SimplifiedFont->NumberOfNarrowGlyphs = (UINT16)(mNarrowFontSize / sizeof (EFI_NARROW_GLYPH));
Location = (UINT8 *)(&SimplifiedFont->NumberOfWideGlyphs + 1);
CopyMem (Location, gUsStdNarrowGlyphData, mNarrowFontSize);
//
// Add this simplified font package to a package list then install it.
//
mHiiHandle = HiiAddPackages (
&mFontPackageListGuid,
NULL,
Package,
NULL
);
ASSERT (mHiiHandle != NULL);
FreePool (Package);
具体的数据全部存放在数组gUsStdNarrowGlyphData
中,它位于一个独立的文件edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe\LaffStd.c中:
EFI_NARROW_GLYPH gUsStdNarrowGlyphData[] = {
//
// Unicode glyphs from 0x20 to 0x7e are the same as ASCII characters 0x20 to 0x7e
//
{ 0x0020, 0x00, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
},
{ 0x0021, 0x00, { 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }
},
// 后面还有很多的数据
实际上因为英文只需要支持ASCII就可以了,再加上一些特定的图形,所以最终的数据也不会太多。
EFI_HII_FONT_PROTOCOL
下面介绍字体的操作函数,对应的就是EFI_HII_FONT_PROTOCOL
,其形式如下:
///
/// The protocol provides the service to retrieve the font informations.
///
struct _EFI_HII_FONT_PROTOCOL {
EFI_HII_STRING_TO_IMAGE StringToImage;
EFI_HII_STRING_ID_TO_IMAGE StringIdToImage;
EFI_HII_GET_GLYPH GetGlyph;
EFI_HII_GET_FONT_INFO GetFontInfo;
};
前面两个都是直接的显示函数,不同的是StringToImage
直接输出字符串,而StringIdToImage
根据uni文件创建的数据,通过StringId
来找到字符串并输出。后两个函数则获取字体和字形的信息。【UEFI实战】UEFI图形显示(从像素到字符)的例子中,通过手动构建字形的方式写出了一个A,而这里就可以通过GetGlyph()
来得到字形并输出,不再需要手动构建,下面是一个示例:
VOID
ShowB (
IN EFI_HII_FONT_PROTOCOL *HiiFont,
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop
)
{
EFI_STATUS Status = EFI_ABORTED;
EFI_IMAGE_OUTPUT *Blt = NULL;
Status = HiiFont->GetGlyph (
HiiFont,
L'B',
NULL,
&Blt,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Failed. - %r\n", __FUNCTION__, __LINE__, Status));
} else {
Gop->Blt (
Gop,
Blt->Image.Bitmap,
EfiBltBufferToVideo,
0,
0,
0,
0,
Blt->Width,
Blt->Height,
0
);
}
}
输出的结果:
汉字输出
前面介绍了如何输出英文,这里简单说明如何在UEFI下使用汉字。对于该问题,其实原理本身是比较简单的,输出英文使用的是窄体字,即一个8x19的像素,而对于汉字显然是不够的,所以UEFI规范中还定义了宽体字:
///
/// The EFI_WIDE_GLYPH has a preferred dimension (w x h) of 16 x 19 pixels, which is large enough
/// to accommodate logographic characters.
///
typedef struct {
///
/// The Unicode representation of the glyph. The term weight is the
/// technical term for a character code.
///
CHAR16 UnicodeWeight;
///
/// The data element containing the glyph definitions.
///
UINT8 Attributes;
///
/// The column major glyph representation of the character. Bits
/// with values of one indicate that the corresponding pixel is to be
/// on when normally displayed; those with zero are off.
///
UINT8 GlyphCol1[EFI_GLYPH_HEIGHT];
///
/// The column major glyph representation of the character. Bits
/// with values of one indicate that the corresponding pixel is to be
/// on when normally displayed; those with zero are off.
///
UINT8 GlyphCol2[EFI_GLYPH_HEIGHT];
///
/// Ensures that sizeof (EFI_WIDE_GLYPH) is twice the
/// sizeof (EFI_NARROW_GLYPH). The contents of Pad must
/// be zero.
///
UINT8 Pad[3];
} EFI_WIDE_GLYPH;
相比于窄体字,重点在于通过两个8x19的像素,每一个输出半个汉字,最终就组成了完整的汉字。而之后的重点就是一个汉字的像素该如何构建,这个在uefi-programming/book/GUIbasics/font/SimpleFont/createdata.html at master · zhenghuadai/uefi-programming · GitHub已经给出了工具,可以创建完整的EFI_WIDE_GLYPH
数组来表示所有的汉字(由于很多字体是有版权的,所以使用的时候需要注意,最好自己参照免费的字体构造),这里直接使用该工具得到数组,然后再使用跟注册窄体字一样的方式来注册汉字,对应的代码:
//
// Reference:
// edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe\GraphicsConsole.c
//
PackageLength = sizeof (EFI_HII_SIMPLE_FONT_PACKAGE_HDR) + mWideFontSize + 4;
Package = AllocateZeroPool (PackageLength);
if (NULL == Package) {
DEBUG ((EFI_D_ERROR, "[%a][%d] Out of memory\n", __FUNCTION__, __LINE__));
return EFI_OUT_OF_RESOURCES;
}
//
// Without this code, system will hang.
//
WriteUnaligned32 ((UINT32 *)Package, PackageLength);
SimplifiedFont = (EFI_HII_SIMPLE_FONT_PACKAGE_HDR *)(Package + 4);
SimplifiedFont->Header.Length = (UINT32)(PackageLength - 4);
SimplifiedFont->Header.Type = EFI_HII_PACKAGE_SIMPLE_FONTS;
SimplifiedFont->NumberOfNarrowGlyphs = 0;
SimplifiedFont->NumberOfWideGlyphs = (UINT16) (mWideFontSize / sizeof (EFI_WIDE_GLYPH));
Location = (UINT8 *)(&SimplifiedFont->NumberOfWideGlyphs + 1);
CopyMem (Location, gWideGlyphData, mWideFontSize);
mHiiHandle = HiiAddPackages (
&mFontPackageListGuid,
NULL,
Package,
NULL
);
if (NULL == mHiiHandle) {
DEBUG ((EFI_D_ERROR, "[%a][%d] NULL == mHiiHandle\n", __FUNCTION__, __LINE__));
Status = EFI_NOT_READY;
} else {
DEBUG ((EFI_D_ERROR, "HanFont added\n"));
}
FreePool (Package);
gWideGlyphData
中存放了所有的汉字字形表示,由于数据太多这里不再列出,只要注册了之后就可以直接使用了,下面是一个代码示例:
Print (L"*********************** 图形和字体测试 ***********************\r\n");
得到的结果:
不过需要注意编译的时候要保证对应的c文件使用GBK(或者其它汉字的编码格式)的编码格式,否则会编译失败:
SearchString: Error while processing file