# 52. Russian 글꼴 추가 - Part 1.

&#x20;UNI 파일은 UEFI의 로컬화를 위한 것이다. 지금까지 우리는, UNI 파일에서 영어와 프랑스어만 출력해 보았다. 이 두 언어의 공통점은, 라틴어 문자 집합의 기호를 사용한다는 점에서 같다.

&#x20;다른 문자 집합에서 문자열을 출력하려면 올바른 문장이 출력되지 않는다. 예를 들어, 간단하게 Hello를 러시아어로 출력하려면,

```
Print(L"Привет!\n");
```

&#x20;다음과 같이 출력된다.

<figure><img src="https://224378457-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgAFiQ5sYs9AjXHJxMFpe%2Fuploads%2FRZPnSa8qt0gebqpbIwZx%2Fimage.png?alt=media&#x26;token=305e1a78-3e36-437e-914b-41b68f9f7125" alt=""><figcaption></figcaption></figure>

&#x20;`nographic` 모드에서는 다음과 같이 출력될 것이다.

```
FS0:\> HIIFont.efi
?@825B!
```

&#x20;이 출력을 구현하는 것에는 신경쓰지 말자. 이는 다른 번역 레벨에서 발생하는 것이고, 이번 장에서는 UEFI Graphic(Native 또는 VNC)을 살펴보도록 하자.

&#x20;어쨌건 출력으로 보이듯이, `!` 하나만 출력된 것을 볼 수 있다. UEFI 시스템 자체에는 러시아어 글꼴이 없기 때문에 위와 같은 일이 발생한 것이다.

&#x20;컴퓨터의 언어로 해석하자면, 러시아어 유니코드 기호 코드를 기호 이미지로 변환하는 방법을 UEFI 시스템은 모르기 때문이다.

&#x20;글꼴 정보는 HII DB에 저장되므로, 문제를 해결하려면 러시아 코드가 있는 유니코드 기호에 대한 "그림"이 있는 글꼴 유형 패키지가 포함된 패키지 목록을 제공하기만 하면 된다.

&#x20;UEFI는 작게는 8x19에 넓게는 16x19 글꼴은 사용한다. 코드에서 기호 데이터는 `EFI_NARROW_GLYPH` 및 `EFI_WIDE_GLYPH`(`UefiInternalFormRepresentation.h`) 구조로 인코딩된다.

```
$ cat MdePkg/Include/Uefi/UefiInternalFormRepresentation.h
---
#define EFI_GLYPH_HEIGHT                     19
#define EFI_GLYPH_WIDTH                      8
///@}

///
/// The EFI_NARROW_GLYPH has a preferred dimension (w x h) of 8 x 19 pixels.
///
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];
} EFI_NARROW_GLYPH;

///
/// 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;
```

&#x20;

&#x20;이유는 모르겠지만, `mFontBin` 구조의 String.c에서 일부 히브리어 문자에 대한 예를 찾아볼 수 있었다. 이 구조에서 유니코드 `0x05d2`가 있는 기호 하나를 살펴보도록 하겠다.

&#x20;`GlyphCol1` 배열을 보고, 이진 시스템에서 배열 데이터를 인쇄해보자. `X/-`로도 표현해 보았다.

```
$ cat MdeModulePkg/Application/UiApp/String.c
---
        {
      0x05d2,
      0x00,
      {
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x78,      // 01111000 // -XXXX---
        0x7C,      // 01111100 // -XXXXX--
        0x0C,      // 00001100 // ----XX--
        0x0C,      // 00001100 // ----XX--
        0x0C,      // 00001100 // ----XX--
        0x0C,      // 00001100 // ----XX--
        0x0C,      // 00001100 // ----XX--
        0x0C,      // 00001100 // ----XX--
        0x1C,      // 00011100 // ---XXX--
        0x3E,      // 00111110 // --XXXXX-
        0x66,      // 01100110 // -XX--XX-
        0x66,      // 01100110 // -XX--XX-
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00       // 00000000 // --------
      }
    },
```

&#x20;이는 실제 히브리어 문자인 gimel `&#0x05D2`(`U+05D2`)로 보인다.\
<https://unicodemap.org/details/0x05D2/index.html>

&#x20;EDKII 코드베이스에 기호에 대한 에는 없지만, 간단하다. `GlyphCol1`은 기호 이미지의 왼쪽 절반을 인코딩하고, `GlyphCol2`는 남은 오른쪽 절반을 인코딩한다.

&#x20;예를 들어, `A`는 다음과 같을 수있다.

```
    {
      0x05d2,
      0x00,
      {
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x01,      // 00000001 // -------X
        0x02,      // 00000010 // ------X-
        0x02,      // 00000010 // ------X-
        0x04,      // 00000100 // -----X--
        0x04,      // 00000100 // -----X--
        0x08,      // 00001000 // ----X---
        0x0F,      // 00001111 // ----XXXX
        0x10,      // 00010000 // ---X----
        0x10,      // 00010000 // ---X----
        0x20,      // 00100000 // --X-----
        0x20,      // 00100000 // --X-----
        0x70,      // 01110000 // -XXX----
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00       // 00000000 // --------
      },
      {
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x80,      // 10000000 // X-------
        0x80,      // 10000000 // X-------
        0x40,      // 01000000 // -X------
        0x40,      // 01000000 // -X------
        0x20,      // 00100000 // --X-----
        0xE0,      // 11100000 // XXX-----
        0x10,      // 00010000 // ---X----
        0x10,      // 00010000 // ---X----
        0x08,      // 00001000 // ----X---
        0x08,      // 00001000 // ----X---
        0x1C,      // 00011100 // ---XXX--
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00,      // 00000000 // --------
        0x00       // 00000000 // --------
      },
      {
        0x00,
        0x00,
        0x00
      }
    },
```

## Glyph 기본 글꼴

&#x20;기본 글꼴의 정의 위치가 궁금할 경우, `LaffStd.c`를 참조하는 것을 추천한다.

```
$ cat MdeModulePkg/Universal/Console/GraphicsConsoleDxe/LaffStd.c
---
EFI_NARROW_GLYPH  gUsStdNarrowGlyphData[] = {
  { 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}},
  { 0x0022, 0x00, {0x00,0x00,0x00,0x6C,0x6C,0x6C,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}},
  ...
  { 0x0000, 0x00, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}} //EOL
};

// Get available Unicode glyphs narrow fonts(8*19 pixels) size.
UINT32 mNarrowFontSize =  sizeof (gUsStdNarrowGlyphData);
```

&#x20;이 `gUsStdNarrowGlyphData` 배열은 글꼴 패키지를 생성하고, HII 데이터베이스에 등록하는 데 사용된다. 실제 코드는 `MdeModulePkg/Universal/Console/GraphicsConsoleDxe/GraphicsConsole.c` 파일의 `RegisterFontPackage` 함수를 참조하도록 하자.

&#x20;HII 데이터베이스에 패키지 목록을 조사했을 때, 이것이 이 글꼴을 담당했다.

```
PackageList[10]: GUID=F5F219D3-7006-4648-AC8D-D61DFB7BC6AD; size=0x14EC  // mFontPackageListGuid
        Package[0]: type=SIMPLE_FONTS; size=0x14D4
        Package[1]: type=END; size=0x4
```

## 러시아어 글꼴에 대한 Glyph 데이터 가져오기

&#x20;Glyph 데이터를 얻어오는 것은 글꼴을 추가하는 작업에 있어서 가장 어려운 것이다.

&#x20;우선 크기가 `8x19/16x19`인 픽셀 글꼴을 찾아야 하는데, 이는 이미 어려운 작업일 수 있다.\
<https://int10h.org/oldschool-pc-fonts/fontlist/font?ast_premiumexec>\
에서 키릴 문자가 포함된 `AST PremiumExec` 글꼴을 찾았다. 이 글꼴에는 대부분의 키릴 유니코드 블록(U+0400 - U+04FF)의 심볼들을 포함하고 있다.

&#x20;하지만 이 글꼴은 `*.woff` 형식으로 인코딩되어 있으므로, Glyph 데이터로 변환해야 한다. 수동으로 변환해주기에는 어려움이 있기에, <https://github.com/zhenghuadai/uefi-programming/tree/master/book/GUIbasics/font>서 이 작업에 HTML canvas를 사용해보자는 아이디어를 차용했다.

&#x20;

&#x20;HTML canvas의 글꼴에서 각 기호를 하나씩 인쇄한다. 각 기호에 대해 이미지 비트맵을 가져오고 해당 데이터를 사용하여 UEFI 환경에서 사용할 수 있는 Glyph 배열을 구성한다. 결국 Glyph 배열을 화면에 출력한다.

&#x20;이는 자바스크립트와 관련된 장장이 아니지만, 스크립트에 대한 설명이 필요하다고 생각한다. 먼저 글꼴을 로드하고 canvas와 스크립트 코드를 선언하는 HTML 부분이다.

```html
<html>
  <head>
    <style>
      @font-face {
        font-family: "AST";
        src: url(web_ast_premiumexec.woff) format('woff');
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="32" height="32"></canvas>
    <script type="text/javascript">
      ...
    </script>
  </body>
</html>
```

&#x20;우리의 코드는 핵심, 다음과 같다.

```javascript
const unicode_start_code = 0x0400;
const unicode_end_code = 0x045F;
var f = new FontFace('AST', 'url(web_ast_premiumexec.woff)');
f.load().then(function() {
  document.write(UnicodeToGlyphs(unicode_start_code, unicode_end_code));
})
```

&#x20;글꼴이 로드되면, canvas에 U+0400에서 U+045F까지의 유니코드 기호를 인쇄하고, 데이터를 조사하고 화면의 UEFI에 대한 최종 C 배열을 출력하는 사용자 지정 함수 `UnicodeToGlyphs`를 실행한다.

&#x20;그리고 여기에 나머지 자바스크립트코드가 있다. 이는 매우 간단하므로, 이해하기 어렵지 않을 것이라 생각한다.

```javascript
function decimalToHex(d, padding) {
  var hex = Number(d).toString(16);
  padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;

  while (hex.length < padding) {
    hex = "0" + hex;
  }

  return hex;
}

function UnicodeToGlyphs(unicode_start_code, unicode_end_code) {
  const threshold = 100;                // `A` threshold to count data as a black pixel
  const FW = 16;
  const FH = 19;
  const left_glyph_start_column = 0;
  const left_glyph_end_column = FW/2 - 1;
  const right_glyph_start_column = FW/2;
  const right_glyph_end_column = FW - 1;

  const canvas = document.getElementById('canvas');
  canvas.width *= window.devicePixelRatio
  canvas.height *= window.devicePixelRatio
  canvas.style.width = 32
  canvas.style.height = 32
  const ctx = canvas.getContext('2d');
  ctx.strokeRect(0, 0, FW, FH);
  ctx.font = "19px AST"
  ctx.fillstyle='#00f';

  var wide_glyphs_str="EFI_WIDE_GLYPH  gSimpleFontWideGlyphData[] = {<BR>";
  var narrow_glyphs_str="EFI_NARROW_GLYPH  gSimpleFontNarrowGlyphData[] = {<BR>";
  for(i=unicode_start_code; i<=unicode_end_code; i++){
    wide_glyph = false;
    ctx.clearRect(0, 0, FW, FH);
    ctx.fillText(String.fromCharCode(i), 0, 0 + FW-1);
    var bitmapimg = ctx.getImageData(0, 0, FW, FH);
    var bitmap = bitmapimg.data;
    var left_glyph = " ";
    var right_glyph = " ";
    for (row=0; row<FH; row++){
      left_row = 0;
      for (col=left_glyph_start_column; col<=left_glyph_end_column; col++){
        left_row <<= 1
        if (bitmap[row*FW*4 + col*4 + 3] > threshold)
          left_row |= 1;
      }
      left_glyph += "0x" + decimalToHex(left_row);

      right_row = 0;
      for (col=right_glyph_start_column; col<=right_glyph_end_column; col++){
        right_row <<= 1
        if(bitmap[row*FW*4 + col*4 + 3] > threshold) {
          wide_glyph = true;
          right_row |= 1;
        }
      }
      right_glyph += "0x" + decimalToHex(right_row);

      if (row < (FH-1)) {
        left_glyph += ",";
        right_glyph += ",";
      }
    }
    if (wide_glyph)
      wide_glyphs_str += "{ 0x" + decimalToHex(i) + ", 0x00, " + "{" +  left_glyph + "}, {" + right_glyph + "}, {0x00,0x00,0x00}}," + "<BR>";
    else
      narrow_glyphs_str += "{ 0x" + decimalToHex(i) + ", 0x00, " + "{" +  left_glyph + "}},"+ "<BR>";
  }
  narrow_glyphs_str += "};<BR>"
  narrow_glyphs_str += "UINT32 gSimpleFontNarrowBytes = sizeof(gSimpleFontNarrowGlyphData);<BR>"
  wide_glyphs_str += "{ 0x00, 0x00, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}" + "<BR>"
  wide_glyphs_str += "};<BR>"
  wide_glyphs_str += "UINT32 gSimpleFontWideBytes = sizeof(gSimpleFontWideGlyphData);<BR>"
  return wide_glyphs_str + "<BR>" + narrow_glyphs_str;
}
```

&#x20;`create_font_data.html`과 `web_ast_premiumexec.woff`를 두고 `create_font_data.html`을 연다. 이 작업을 위해 Chrome(version 95.0.4638.69)을 사용했다. 페이지는 다음과 같이 출력되어야 한다.

```
EFI_WIDE_GLYPH gSimpleFontWideGlyphData[] = {
{ 0x00, 0x00, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}
};
UINT32 gSimpleFontWideBytes = sizeof(gSimpleFontWideGlyphData);

EFI_NARROW_GLYPH gSimpleFontNarrowGlyphData[] = {
{ 0x400, 0x00, { 0x60,0x30,0x00,0xfe,0x66,0x62,0x60,0x68,0x78,0x68,0x60,0x60,0x62,0x66,0xfe,0x00,0x00,0x00,0x00}},
{ 0x401, 0x00, { 0x66,0x66,0x00,0xfe,0x66,0x62,0x60,0x68,0x78,0x68,0x60,0x60,0x62,0x66,0xfe,0x00,0x00,0x00,0x00}},
{ 0x402, 0x00, { 0x00,0x00,0x00,0xfc,0x64,0x60,0x60,0x6c,0x76,0x66,0x66,0x66,0x66,0x66,0xe6,0x0c,0x00,0x00,0x00}},
{ 0x403, 0x00, { 0x0c,0x18,0x00,0xfe,0x66,0x62,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0xf0,0x00,0x00,0x00,0x00}},
 ...
{ 0x45c, 0x00, { 0x00,0x00,0x00,0x0c,0x18,0x30,0x00,0xe6,0x66,0x6c,0x78,0x78,0x6c,0x66,0xe6,0x00,0x00,0x00,0x00}},
{ 0x45d, 0x00, { 0x00,0x00,0x00,0x60,0x30,0x18,0x00,0xc6,0xc6,0xce,0xde,0xf6,0xe6,0xc6,0xc6,0x00,0x00,0x00,0x00}},
{ 0x45e, 0x00, { 0x00,0x00,0x00,0x00,0x6c,0x38,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x7e,0x06,0x06,0x0c,0xf8,0x00}},
{ 0x45f, 0x00, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xee,0x6c,0x6c,0x6c,0x6c,0x6c,0x6c,0xfe,0x10,0x10,0x00,0x00}},
};
UINT32 gSimpleFontNarrowBytes = sizeof(gSimpleFontNarrowGlyphData);
```

&#x20;우리가 가져온 글꼴은 고정된 크기인 8x19 문자만 포함하므로, `gSimpleFontNarrowGlyphData` 배열만 데이터로 채워진다. 이제 UEFI 애플리케이션을 구성하고, 글꼴을 HII 데이터베이스에 추가할 때이다.
