2023-04-13 13:16:14 +06:00

780 lines
19 KiB
C

/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Base.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseOverflowLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include "OpenCanopy.h"
#include "BmfLib.h"
#include "GuiApp.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;
UINT16 MaxHeight;
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;
MaxHeight = 0;
for (Index = 0; Index < Context->NumChars; ++Index) {
Result = BaseOverflowAddS32 (
Chars[Index].yoffset,
Chars[Index].height,
&Height
);
Result |= BaseOverflowAddS32 (
Chars[Index].xoffset,
Chars[Index].width,
&Width
);
Result |= BaseOverflowAddS32 (
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;
}
MaxHeight = MAX (MaxHeight, (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 ();
}
Context->Height = (UINT16)MaxHeight;
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 = BaseOverflowAddS32 (
Char->xoffset + Char->width,
Pairs[Index].amount,
&Width
);
Result |= BaseOverflowAddS32 (
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 UINT8 PosX,
IN UINT8 PosY
)
{
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 = (INT32)PosX + 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 = BaseOverflowAddS32 (Width, CurWidth, &Width);
if (Result) {
DEBUG ((DEBUG_WARN, "BMF: width overflows\n"));
FreePool (TextInfo);
return NULL;
}
TextInfo->Chars[Index] = Char;
InfoPairs[Index - 1] = Pair;
}
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;
ASSERT (PosY + Context->Height >= PosY);
TextInfo->Height = PosY + Context->Height;
TextInfo->OffsetY = PosY;
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) {
if (AlphaSrc->Red != 0) {
//
// 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;
INT32 OffsetY;
ASSERT (LabelImage != NULL);
ASSERT (Context != NULL);
ASSERT (String != NULL);
TextInfo = BmfGetTextInfo (
&Context->BmfContext,
String,
StringLen,
BOOT_ENTRY_LABEL_TEXT_OFFSET * Context->Scale,
BOOT_ENTRY_LABEL_TEXT_OFFSET * Context->Scale
);
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 = 2 * Context->Scale;
InitialCharX = -TextInfo->Chars[0]->xoffset;
InitialWidthOffset = TextInfo->Chars[0]->xoffset;
for (Index = 0; Index < StringLen; ++Index) {
OffsetY = TextInfo->Chars[Index]->yoffset + TextInfo->OffsetY;
if (OffsetY < 0) {
OffsetY = 0;
DEBUG ((
DEBUG_INFO,
"BMF: Char %d y-offset off-screen by %d pixels\n",
TextInfo->Chars[Index]->id,
-OffsetY
));
}
ASSERT (TextInfo->Chars[Index]->yoffset + TextInfo->OffsetY >= 0);
for (
RowIndex = 0,
SourceRowOffset = TextInfo->Chars[Index]->y * Context->FontImage.Width,
TargetRowOffset = 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,
IN UINT8 Scale
)
{
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;
}
Context->Scale = Scale;
// 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;
}
}