【UEFI实战】UEFI图形显示(字符输出)

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数据库基础中字体相关的结构体:

【UEFI实战】UEFI图形显示(字符输出)_第1张图片

从图中可以看出,字体分为两种,一种是普通版本另一种是简化版本,本节将分别介绍。

最左边的结构体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;

重要的是后面的三个成员,表示两类信息,FontPackageFontInfo。其中包含的字体和字形的所有信息。关于字体(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;

这些值与字符的对应关系图如下所示:

【UEFI实战】UEFI图形显示(字符输出)_第2张图片

剩下的结构体EFI_HII_FONT_PACKAGE_HDRHII_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;
  // 后略
};

这里不写它的函数原型,而是直接关注对于字体来说其实现中最重要的的部分,其调用流程如下:

HiiNewPackageList
AddPackages
InsertFontPackage
InsertSimpleFontPackage

字体注册数据的位置在前面介绍过的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实战】UEFI图形显示(字符输出)_第3张图片

汉字输出

前面介绍了如何输出英文,这里简单说明如何在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");

得到的结果:

【UEFI实战】UEFI图形显示(字符输出)_第4张图片

不过需要注意编译的时候要保证对应的c文件使用GBK(或者其它汉字的编码格式)的编码格式,否则会编译失败:

SearchString: Error while processing file

你可能感兴趣的:(uefi)