/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
**/
#include
#include
#include
#include
#include
#include "OpenCanopy.h"
#include "BmfLib.h"
CONST BMF_CHAR *
BmfGetChar (
IN CONST BMF_CONTEXT *Context,
IN UINT32 Char
)
{
CONST BMF_CHAR *Chars;
UINTN Left;
UINTN Right;
UINTN Median;
UINTN Index;
ASSERT (Context != NULL);
Chars = Context->Chars;
for (Index = 0; Index < 2; ++Index) {
//
// Binary Search for the character as the list is sorted.
//
Left = 0;
//
// As duplicates are not allowed, Right can be ceiled with Char.
//
Right = MIN (Context->NumChars, Char) - 1;
while (Left <= Right) {
//
// This cannot wrap around due to the file size limitation.
//
Median = (Left + Right) / 2;
if (Chars[Median].id == Char) {
return &Chars[Median];
} else if (Chars[Median].id < Char) {
Left = Median + 1;
} else {
Right = Median - 1;
}
}
//
// Fallback to underscore on not found symbols.
//
Char = '_';
}
//
// Supplied font does not support even underscores.
//
return NULL;
}
CONST BMF_KERNING_PAIR *
BmfGetKerningPair (
IN CONST BMF_CONTEXT *Context,
IN CHAR16 Char1,
IN CHAR16 Char2
)
{
CONST BMF_KERNING_PAIR *Pairs;
UINTN Left;
UINTN Right;
UINTN Median;
UINTN Index;
ASSERT (Context != NULL);
Pairs = Context->KerningPairs;
if (Pairs == NULL) {
return NULL;
}
//
// Binary Search for the first character as the list is sorted.
//
Left = 0;
Right = Context->NumKerningPairs - 1;
while (Left <= Right) {
//
// This cannot wrap around due to the file size limitation.
//
Median = (Left + Right) / 2;
if (Pairs[Median].first == Char1) {
//
// Flat search for the second character as these are usually a lot less,
// the list is sorted (relative to the first character).
//
if (Pairs[Median].second == Char2) {
return &Pairs[Median];
} else if (Pairs[Median].second < Char2) {
for (
Index = Median + 1;
Index < Context->NumKerningPairs && Pairs[Index].first == Char1;
++Index
) {
if (Pairs[Index].second == Char2) {
return &Pairs[Index];
}
}
} else {
Index = Median;
while (Index > 0) {
--Index;
if (Pairs[Index].first != Char1) {
break;
}
if (Pairs[Index].second == Char2) {
return &Pairs[Index];
}
}
}
break;
} else if (Pairs[Median].first < Char1) {
Left = Median + 1;
} else {
Right = Median - 1;
}
}
return NULL;
}
BOOLEAN
BmfContextInitialize (
OUT BMF_CONTEXT *Context,
IN CONST VOID *FileBuffer,
IN UINT32 FileSize
)
{
BOOLEAN Result;
CONST BMF_HEADER *Header;
CONST BMF_BLOCK_HEADER *Block;
UINTN Index;
INT16 MinY;
UINT16 MaxY;
INT32 Height;
INT32 Width;
INT32 Advance;
CONST BMF_CHAR *Chars;
CONST BMF_KERNING_PAIR *Pairs;
CONST BMF_CHAR *Char;
ASSERT (Context != NULL);
ASSERT (FileBuffer != NULL);
ASSERT (FileSize > 0);
Header = FileBuffer;
//
// Limit file size for sanity reason and to guarantee wrapping around in BS
// is not going to happen.
//
if (FileSize < sizeof (*Header) || FileSize > BASE_2MB) {
DEBUG ((DEBUG_WARN, "BMF: FileSize insane\n"));
return FALSE;
}
if (Header->signature[0] != 'B'
|| Header->signature[1] != 'M'
|| Header->signature[2] != 'F'
|| Header->version != 3) {
DEBUG ((DEBUG_WARN, "BMF: Header insane\n"));
return FALSE;
}
ZeroMem (Context, sizeof (*Context));
FileSize -= sizeof (*Header);
Block = (CONST BMF_BLOCK_HEADER *)(Header + 1);
while (FileSize >= sizeof (*Block)) {
FileSize -= sizeof (*Block);
if (FileSize < Block->size) {
DEBUG ((DEBUG_WARN, "BMF: Block insane %u vs %u\n", FileSize, Block->size));
return FALSE;
}
FileSize -= Block->size;
switch (Block->identifier) {
case BMF_BLOCK_INFO_ID:
{
if (Block->size < sizeof (BMF_BLOCK_INFO)) {
DEBUG ((DEBUG_WARN, "BMF: BlockInfo insane\n"));
return FALSE;
}
Context->Info = (CONST BMF_BLOCK_INFO *)(Block + 1);
DEBUG ((
DEBUG_INFO,
"OCUI: Info->fontSize %u Info->bitField %u Info->charSet %u Info->stretchH %u Info->aa %u\n",
Context->Info->fontSize,
Context->Info->bitField,
Context->Info->charSet,
Context->Info->stretchH,
Context->Info->aa
));
DEBUG ((
DEBUG_INFO,
"OCUI: Info->paddingUp %u Info->paddingRight %u Info->paddingDown %u Info->paddingLeft %u\n",
Context->Info->paddingUp,
Context->Info->paddingRight,
Context->Info->paddingDown,
Context->Info->paddingLeft
));
DEBUG ((
DEBUG_INFO,
"OCUI: Info->spacingHoriz %u Info->spacingVert %u Info->outline %u Info->fontName %a\n",
Context->Info->spacingHoriz,
Context->Info->spacingVert,
Context->Info->outline,
Context->Info->fontName
));
/*if ((Context->Info->bitField & BMF_BLOCK_INFO_BF_UNICODE) == 0) {
DEBUG ((DEBUG_WARN, "BMF: Only Unicode is supported\n"));
return FALSE;
}*/
break;
}
case BMF_BLOCK_COMMON_ID:
{
if (Block->size != sizeof (BMF_BLOCK_COMMON)) {
DEBUG ((DEBUG_WARN, "BMF: Block Common insane\n"));
return FALSE;
}
Context->Common = (CONST BMF_BLOCK_COMMON *)(Block + 1);
if (Context->Common->pages != 1) {
DEBUG ((DEBUG_WARN, "BMF: Only one page is supported\n"));
return FALSE;
}
break;
}
case BMF_BLOCK_PAGES_ID:
{
Context->Pages = (CONST BMF_BLOCK_PAGES *)(Block + 1);
break;
}
case BMF_BLOCK_CHARS_ID:
{
if (Block->size % sizeof (BMF_CHAR) != 0) {
DEBUG ((DEBUG_WARN, "BMF: Block chars size unaligned\n"));
return FALSE;
}
Context->Chars = (CONST BMF_BLOCK_CHARS *)(Block + 1);
Context->NumChars = Block->size / sizeof (BMF_CHAR);
break;
}
case BMF_BLOCK_KERNING_PAIRS_ID:
{
if (Block->size % sizeof (BMF_KERNING_PAIR) != 0) {
DEBUG ((DEBUG_WARN, "BMF: Block Pairs unaligned\n"));
return FALSE;
}
Context->KerningPairs = (CONST BMF_BLOCK_KERNING_PAIRS *)(Block + 1);
Context->NumKerningPairs = Block->size / sizeof (BMF_KERNING_PAIR);
break;
}
default:
{
//
// Ignore potential trailer.
//
FileSize = 0;
break;
}
}
Block = (CONST BMF_BLOCK_HEADER *)((UINTN)(Block + 1) + Block->size);
}
if (Context->Info == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Missing Info block\n"));
return FALSE;
}
if (Context->Common == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Missing Common block\n"));
return FALSE;
}
if (Context->Pages == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Missing Pages block\n"));
return FALSE;
}
if (Context->Chars == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Missing Chars block\n"));
return FALSE;
}
Chars = Context->Chars;
MinY = MAX_INT16;
MaxY = 0;
for (Index = 0; Index < Context->NumChars; ++Index) {
Result = OcOverflowAddS32 (
Chars[Index].yoffset,
Chars[Index].height,
&Height
);
Result |= OcOverflowAddS32 (
Chars[Index].xoffset,
Chars[Index].width,
&Width
);
Result |= OcOverflowAddS32 (
Chars[Index].xoffset,
Chars[Index].xadvance,
&Advance
);
if (Result
|| 0 > Height || Height > MAX_UINT16
|| 0 > Width || Width > MAX_UINT16
|| 0 > Advance || Advance > MAX_UINT16
|| Chars[Index].xadvance < 0) {
DEBUG ((
DEBUG_WARN,
"BMF: Char insane\n"
" id %u\n"
" x %u\n"
" y %u\n"
" width %u\n"
" height %u\n"
" xoffset %d\n"
" yoffset %d\n"
" xadvance %d\n"
" page %u\n"
" chnl %u\n",
Chars[Index].id,
Chars[Index].x,
Chars[Index].y,
Chars[Index].width,
Chars[Index].height,
Chars[Index].xoffset,
Chars[Index].yoffset,
Chars[Index].xadvance,
Chars[Index].page,
Chars[Index].chnl
));
return FALSE;
}
MinY = MIN (MinY, Chars[Index].yoffset);
MaxY = MAX (MaxY, (UINT16) Height);
//
// This only yields unexpected but not undefined behaviour when not met,
// hence it is fine verifying it only DEBUG mode.
//
DEBUG_CODE_BEGIN ();
if (Index > 0) {
if (Chars[Index - 1].id > Chars[Index].id) {
DEBUG ((DEBUG_WARN, "BMF: Character IDs are not sorted\n"));
return FALSE;
} else if (Chars[Index - 1].id == Chars[Index].id) {
DEBUG ((DEBUG_WARN, "BMF: Character ID duplicate\n"));
return FALSE;
}
}
DEBUG_CODE_END ();
}
Result = OcOverflowSubS32 (
MaxY,
MinY,
&Height
);
if (Result
|| 0 >= Height || Height > MAX_UINT16) {
DEBUG ((
DEBUG_WARN,
"BMF: Insane font Y info %d %d\n",
MaxY,
MinY
));
return FALSE;
}
Context->Height = Context->Common->lineHeight;
Context->OffsetY = -MinY;
Pairs = Context->KerningPairs;
if (Pairs != NULL) { // According to the docs, kerning pairs are optional
for (Index = 0; Index < Context->NumKerningPairs; ++Index) {
Char = BmfGetChar (Context, Pairs[Index].first);
if (Char == NULL) {
DEBUG ((
DEBUG_WARN,
"BMF: Pair char %u not found\n",
Pairs[Index].first
));
return FALSE;
}
Result = OcOverflowAddS32 (
Char->xoffset + Char->width,
Pairs[Index].amount,
&Width
);
Result |= OcOverflowAddS32 (
Char->xoffset + Char->xadvance,
Pairs[Index].amount,
&Advance
);
if (Result
|| 0 > Width || Width > MAX_UINT16
|| 0 > Advance || Advance > MAX_UINT16) {
DEBUG ((
DEBUG_WARN,
"BMF: Pair at index %d insane: first %u, second %u, amount %d\n",
Index,
Pairs[Index].first,
Pairs[Index].second,
Pairs[Index].amount
));
return FALSE;
}
//
// This only yields unexpected but not undefined behaviour when not met,
// hence it is fine verifying it only DEBUG mode.
//
DEBUG_CODE_BEGIN ();
if (Index > 0) {
if (Pairs[Index - 1].first > Pairs[Index].first) {
DEBUG ((DEBUG_WARN, "BMF: First Character IDs are not sorted\n"));
return FALSE;
}
if (Pairs[Index - 1].first == Pairs[Index].first) {
if (Pairs[Index - 1].second > Pairs[Index].second) {
DEBUG ((DEBUG_WARN, "BMF: Second Character IDs are not sorted\n"));
return FALSE;
}
if (Pairs[Index - 1].second == Pairs[Index].second) {
DEBUG ((DEBUG_WARN, "BMF: Second Character ID duplicate\n"));
return FALSE;
}
}
}
DEBUG_CODE_END ();
}
}
return TRUE;
}
typedef struct {
UINT16 Width;
UINT16 Height;
INT16 OffsetY;
CONST BMF_CHAR *Chars[];
//CONST BMF_KERNING_PAIR *KerningPairs[];
} BMF_TEXT_INFO;
BMF_TEXT_INFO *
BmfGetTextInfo (
IN CONST BMF_CONTEXT *Context,
IN CONST CHAR16 *String,
IN UINTN StringLen,
IN UINTN MaxWidth
)
{
BOOLEAN Result;
BMF_TEXT_INFO *TextInfo;
CONST BMF_KERNING_PAIR **InfoPairs;
INT32 Width;
INT32 CurWidth;
CONST BMF_CHAR *Char;
CONST BMF_KERNING_PAIR *Pair;
UINTN Index;
ASSERT (Context != NULL);
ASSERT (String != NULL);
if (StringLen == 0) {
return NULL;
}
ASSERT (String[0] != 0);
Char = BmfGetChar (Context, String[0]);
if (Char == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Text Char not found\n"));
return NULL;
}
TextInfo = AllocatePool (
sizeof (*TextInfo)
+ StringLen * sizeof (BMF_CHAR *)
+ StringLen * sizeof (BMF_BLOCK_KERNING_PAIRS *)
);
if (TextInfo == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Out of res\n"));
return NULL;
}
InfoPairs = (CONST BMF_KERNING_PAIR **)&TextInfo->Chars[StringLen];
TextInfo->Chars[0] = Char;
Width = Char->xadvance;
for (Index = 1; Index < StringLen; ++Index) {
ASSERT (String[Index] != 0);
Char = BmfGetChar (Context, String[Index]);
if (Char == NULL) {
DEBUG ((DEBUG_WARN, "BMF: Text Char not found\n"));
FreePool (TextInfo);
return NULL;
}
CurWidth = Char->xadvance;
Pair = BmfGetKerningPair (Context, String[Index - 1], String[Index]);
if (Pair != NULL) {
CurWidth += Pair->amount;
}
Result = OcOverflowAddS32 (Width, CurWidth, &Width);
if (Result) {
DEBUG ((DEBUG_WARN, "BMF: width overflows\n"));
FreePool (TextInfo);
return NULL;
}
/*if (Width > MaxWidth) {
break;
}*/
TextInfo->Chars[Index] = Char;
InfoPairs[Index - 1] = Pair;
}
if (Index != StringLen) {
CONST BMF_KERNING_PAIR *PeriodPair;
Char = BmfGetChar (Context, '.');
if (Char == NULL) {
FreePool (TextInfo);
return NULL;
}
CurWidth = 2 * (INT32)Char->xadvance + (INT32)Char->xoffset + (INT32)Char->width;
PeriodPair = BmfGetKerningPair (Context, '.', '.');
if (PeriodPair != NULL) {
CurWidth += 2 * (INT32)PeriodPair->amount;
}
}
Width += ((INT32)TextInfo->Chars[Index - 1]->xoffset + (INT32)TextInfo->Chars[Index - 1]->width - (INT32)TextInfo->Chars[Index - 1]->xadvance);
InfoPairs[Index - 1] = NULL;
if (Width > MAX_UINT16) {
DEBUG ((DEBUG_WARN, "BMF: Width exceeds bounds\n"));
FreePool (TextInfo);
return NULL;
}
TextInfo->Width = (UINT16)Width;
TextInfo->Height = Context->Height;
TextInfo->OffsetY = Context->OffsetY;
return TextInfo;
}
STATIC
EFI_GRAPHICS_OUTPUT_BLT_PIXEL
mBlack = { 0, 0, 0, 255 };
STATIC
EFI_GRAPHICS_OUTPUT_BLT_PIXEL
mWhite = { 255, 255, 255, 255 };
STATIC
VOID
BlendMem (
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Dst,
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *AlphaSrc,
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Color,
UINTN PixelCount
)
{
UINTN Index;
for (Index = 0; Index < PixelCount; ++Index) {
//
// We assume that the font is generated by dpFontBaker
// and has only gray channel, which should be interpreted as alpha.
//
GuiBlendPixel(Dst, Color, AlphaSrc->Red);
++Dst;
++AlphaSrc;
}
}
BOOLEAN
GuiGetLabel (
OUT GUI_IMAGE *LabelImage,
IN CONST GUI_FONT_CONTEXT *Context,
IN CONST CHAR16 *String,
IN UINTN StringLen,
IN BOOLEAN Inverted
)
{
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Buffer;
BMF_TEXT_INFO *TextInfo;
CONST BMF_KERNING_PAIR **InfoPairs;
UINTN Index;
UINT16 RowIndex;
UINT32 SourceRowOffset;
UINT32 TargetRowOffset;
INT32 TargetCharX;
INT32 InitialCharX;
INT32 InitialWidthOffset;
ASSERT (LabelImage != NULL);
ASSERT (Context != NULL);
ASSERT (String != NULL);
TextInfo = BmfGetTextInfo (&Context->BmfContext, String, StringLen, 0);
if (TextInfo == NULL) {
DEBUG ((DEBUG_WARN, "BMF: GetTextInfo failed\n"));
return FALSE;
}
Buffer = AllocateZeroPool ((UINT32) TextInfo->Width * (UINT32) TextInfo->Height * sizeof (*Buffer));
if (Buffer == NULL) {
DEBUG ((DEBUG_WARN, "BMF: out of res\n"));
FreePool (TextInfo);
return FALSE;
}
InfoPairs = (CONST BMF_KERNING_PAIR **)&TextInfo->Chars[StringLen];
TargetCharX = 0;
if (TextInfo->Chars[0]->xoffset >= 0) {
InitialCharX = 0;
InitialWidthOffset = 0;
} else {
InitialCharX = -TextInfo->Chars[0]->xoffset;
InitialWidthOffset = TextInfo->Chars[0]->xoffset;
}
for (Index = 0; Index < StringLen; ++Index) {
ASSERT (TextInfo->Chars[Index]->yoffset + TextInfo->OffsetY >= 0);
for (
RowIndex = 0,
SourceRowOffset = TextInfo->Chars[Index]->y * Context->FontImage.Width,
TargetRowOffset = (TextInfo->Chars[Index]->yoffset + TextInfo->OffsetY) * TextInfo->Width;
RowIndex < TextInfo->Chars[Index]->height;
++RowIndex,
SourceRowOffset += Context->FontImage.Width,
TargetRowOffset += TextInfo->Width
) {
if (Inverted) {
BlendMem (
&Buffer[TargetRowOffset + TargetCharX + TextInfo->Chars[Index]->xoffset + InitialCharX],
&Context->FontImage.Buffer[SourceRowOffset + TextInfo->Chars[Index]->x + InitialCharX],
&mBlack,
(TextInfo->Chars[Index]->width + InitialWidthOffset)
);
} else {
BlendMem (
&Buffer[TargetRowOffset + TargetCharX + TextInfo->Chars[Index]->xoffset + InitialCharX],
&Context->FontImage.Buffer[SourceRowOffset + TextInfo->Chars[Index]->x + InitialCharX],
&mWhite,
(TextInfo->Chars[Index]->width + InitialWidthOffset)
);
}
}
TargetCharX += TextInfo->Chars[Index]->xadvance;
if (InfoPairs[Index] != NULL) {
TargetCharX += InfoPairs[Index]->amount;
}
InitialCharX = 0;
InitialWidthOffset = 0;
}
LabelImage->Width = TextInfo->Width;
LabelImage->Height = TextInfo->Height;
LabelImage->Buffer = Buffer;
FreePool (TextInfo);
return TRUE;
}
BOOLEAN
GuiFontConstruct (
OUT GUI_FONT_CONTEXT *Context,
IN VOID *FontImage,
IN UINTN FontImageSize,
IN VOID *FileBuffer,
IN UINT32 FileSize
)
{
EFI_STATUS Status;
BOOLEAN Result;
ASSERT (Context != NULL);
ASSERT (FontImage != NULL);
ASSERT (FontImageSize > 0);
ASSERT (FileBuffer != NULL);
ASSERT (FileSize > 0);
ZeroMem (Context, sizeof (*Context));
Context->KerningData = FileBuffer;
Status = GuiPngToImage (
&Context->FontImage,
FontImage,
FontImageSize,
FALSE
);
FreePool (FontImage);
if (EFI_ERROR (Status)) {
GuiFontDestruct (Context);
return FALSE;
}
Result = BmfContextInitialize (&Context->BmfContext, FileBuffer, FileSize);
if (!Result) {
GuiFontDestruct (Context);
return FALSE;
}
// TODO: check file size
return TRUE;
}
VOID
GuiFontDestruct (
IN GUI_FONT_CONTEXT *Context
)
{
ASSERT (Context != NULL);
if (Context->FontImage.Buffer != NULL) {
FreePool (Context->FontImage.Buffer);
Context->FontImage.Buffer = NULL;
}
if (Context->KerningData != NULL) {
FreePool (Context->KerningData);
Context->KerningData = NULL;
}
}