/** @file Load console font from standard format .hex font file - currently supports 8x16 fonts only. Copyright (c) 2023, Mike Beaton. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include "OcConsoleLibInternal.h" #include #include #include #include #define HEX_LINE_DATA_OFFSET ((sizeof(CHAR16) * 2) + 1) #define HEX_LINE_LENGTH (HEX_LINE_DATA_OFFSET + (ISO_CHAR_HEIGHT * sizeof(UINT8) * 2)) #define HEX_FILE_ERROR_PREFIX "OCC: Hex font line " // // Re-use exactly the structure we want, but with a UINTN in the pointer during // construction of the data. (Avoid making this a union, as it is not relevant // to the consumer of a font.) // #define PTR_AS_UINTN(ptr) (*((UINTN *) &ptr)) // // Parse exactly Length upper or lower case hex digits with no prefix to UINTN. // Note: AsciiStrHexToUintn and friends allow prefix and surrounding space and // are auto-terminating (i.e. no specified length), none of which we want here. // STATIC EFI_STATUS AsciiHexToUintn ( IN CHAR8 *Str, OUT UINTN *Data, IN UINTN Length ) { UINT8 Char; if (Data == NULL) { return EFI_INVALID_PARAMETER; } if (Length > sizeof (UINTN) * 2) { return EFI_UNSUPPORTED; } *Data = 0; while (Length-- > 0) { Char = (UINT8)(*Str++); if ((Char >= '0') && (Char <= '9')) { Char -= '0'; } else if ((Char >= 'A') && (Char <= 'F')) { Char -= 'A'; Char += 10; } else if ((Char >= 'a') && (Char <= 'f')) { Char -= 'a'; Char += 10; } else { *Data = 0; return EFI_INVALID_PARAMETER; } *Data = (*Data << 4) + Char; } return EFI_SUCCESS; } // // Line length already checked. // STATIC EFI_STATUS ParseHexFontLine ( UINTN LineNumber, CHAR8 *CurrentLine, CHAR16 *Char, UINT8 *LineGlyphData ) { EFI_STATUS Status; UINTN Data; UINT8 Line; if (CurrentLine[4] != ':') { DEBUG ((DEBUG_WARN, "%a%u illegal line\n", HEX_FILE_ERROR_PREFIX, LineNumber)); return EFI_UNSUPPORTED; } Status = AsciiHexToUintn (CurrentLine, &Data, 4); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "%a%u cannot parse character code\n", HEX_FILE_ERROR_PREFIX, LineNumber)); return EFI_UNSUPPORTED; } *Char = (CHAR16)Data; for (Line = 0; Line < ISO_CHAR_HEIGHT; Line++) { Status = AsciiHexToUintn (&CurrentLine[HEX_LINE_DATA_OFFSET + (Line * 2)], &Data, 2); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "%a%u cannot parse bitmap data\n", HEX_FILE_ERROR_PREFIX, LineNumber)); break; } LineGlyphData[Line] = (UINT8)Data; } return EFI_SUCCESS; } EFI_STATUS OcLoadConsoleFont ( IN OC_STORAGE_CONTEXT *Storage, IN CONST CHAR8 *FontName, OUT OC_CONSOLE_FONT **Font ) { EFI_STATUS Status; CHAR16 Path[OC_STORAGE_SAFE_PATH_MAX]; UINT8 Line; UINTN Pass; CHAR8 *HexFontFile; CHAR8 *CurrentLine; CHAR8 *NextLine; CHAR8 *TrimmedLineEnd; BOOLEAN Finished; UINTN LineNumber; OC_FLEX_ARRAY *FlexFontPages; UINT16 FontPageMin; UINT16 FontPageMax; UINT16 Char; UINT8 LineGlyphData[ISO_CHAR_HEIGHT]; UINT16 PageNumber; UINT8 PageChar; UINT16 CurrentPageNumber; OC_CONSOLE_FONT_PAGE *FontPage; UINT8 PageCharMin; UINT8 PageCharMax; UINT8 FontPageHead; UINT8 FontPageTail; UINT8 GlyphHead; UINT8 GlyphTail; UINT16 PageSparseCount; UINT8 GlyphSparseCount; BOOLEAN FoundNonZero; UINT32 GlyphOffsetsSize; UINT32 GlyphDataSize; UINTN FontSize; UINTN FontHeaderSize; UINTN FontPagesSize; UINTN FontPageOffsetsSize; OC_CONSOLE_FONT_PAGE *FontPages; UINT16 *FontPageOffsets; UINT8 *GlyphOffsets; UINT8 *GlyphData; UINT16 *SavedFontPageOffsets; UINT8 *SavedGlyphOffsets; UINT8 *SavedGlyphData; VOID *SavedEnd; ASSERT (Font != NULL); *Font = NULL; Status = OcUnicodeSafeSPrint ( Path, sizeof (Path), OPEN_CORE_FONT_PATH L"%a.hex", FontName ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCC: Cannot fit %a\n", FontName)); return EFI_OUT_OF_RESOURCES; } HexFontFile = OcStorageReadFileUnicode (Storage, Path, NULL); if (HexFontFile == NULL) { return EFI_NOT_FOUND; } FlexFontPages = OcFlexArrayInit (sizeof (OC_CONSOLE_FONT_PAGE), NULL); if (FlexFontPages == NULL) { FreePool (HexFontFile); return EFI_OUT_OF_RESOURCES; } for (Pass = 1; Pass <= 2; Pass++) { FontPageMin = 0; FontPageMax = 0; CurrentPageNumber = MAX_UINT16; PageSparseCount = 0; GlyphDataSize = 0; GlyphOffsetsSize = 0; NextLine = HexFontFile; LineNumber = 0; while (TRUE) { CurrentLine = NextLine; ++LineNumber; // // Left trim. // while (*CurrentLine != '\n' && OcIsSpace (*CurrentLine)) { ++CurrentLine; } Finished = (*CurrentLine == '\0'); if (!Finished) { NextLine = AsciiStrStr (CurrentLine, "\n"); if (NextLine == NULL) { NextLine = &CurrentLine[AsciiStrLen (CurrentLine)]; } else { ++NextLine; } if (*CurrentLine == '#') { continue; ///< Comment, on separate line only. } // // Right trim. // TrimmedLineEnd = NextLine - 1; while (TrimmedLineEnd >= CurrentLine && OcIsSpace (*TrimmedLineEnd)) { --TrimmedLineEnd; } ++TrimmedLineEnd; if (TrimmedLineEnd == CurrentLine) { continue; ///< Empty line. } if ((TrimmedLineEnd - CurrentLine) != HEX_LINE_LENGTH) { DEBUG ((DEBUG_WARN, "%a%u illegal line length\n", HEX_FILE_ERROR_PREFIX, LineNumber)); Status = EFI_UNSUPPORTED; break; } Status = ParseHexFontLine (LineNumber, CurrentLine, &Char, LineGlyphData); if (EFI_ERROR (Status)) { break; } PageNumber = (Char >> 7) & 0x1FF; PageChar = Char & 0x7F; } if (Finished || (PageNumber != CurrentPageNumber)) { // // Output previous page. // if (CurrentPageNumber != MAX_UINT16) { if (Pass == 1) { FontPage = OcFlexArrayAddItem (FlexFontPages); if (FontPage == NULL) { Status = EFI_OUT_OF_RESOURCES; break; } FontPage->CharMin = PageCharMin; FontPage->CharMax = PageCharMax; if (FontPageHead == ISO_CHAR_HEIGHT) { ASSERT (FontPageTail == ISO_CHAR_HEIGHT); FontPage->FontHead = ISO_CHAR_HEIGHT - (ISO_CHAR_HEIGHT / 2); FontPage->FontTail = (ISO_CHAR_HEIGHT / 2); } else { FontPage->FontHead = FontPageHead; FontPage->FontTail = FontPageTail; } FontPage->Glyphs = NULL; PTR_AS_UINTN (FontPage->GlyphOffsets) = GlyphSparseCount; FontPage->LeftToRight = TRUE; GlyphDataSize += (ISO_CHAR_HEIGHT - FontPageHead - FontPageTail) * GlyphSparseCount; if (GlyphSparseCount < PageCharMax - PageCharMin) { GlyphOffsetsSize += (PageCharMax - PageCharMin); } } else { if (GlyphSparseCount < PageCharMax - PageCharMin) { GlyphOffsets += (PageCharMax - PageCharMin); } } } if (Finished) { break; } CurrentPageNumber = PageNumber; if (FontPageMax == 0) { FontPageMin = CurrentPageNumber; FontPageMax = CurrentPageNumber + 1; } else if (CurrentPageNumber >= FontPageMax) { FontPageMax = CurrentPageNumber + 1; } else { DEBUG ((DEBUG_WARN, "%a%u hex file must be sorted #%u\n", HEX_FILE_ERROR_PREFIX, LineNumber, 1)); Status = EFI_UNSUPPORTED; break; } ++PageSparseCount; PageCharMin = PageChar; if (Pass == 1) { FontPageHead = ISO_CHAR_HEIGHT; FontPageTail = ISO_CHAR_HEIGHT; } else { FontPage = FontPages; ++FontPages; ASSERT (FontPage->CharMin == PageCharMin); ASSERT (FontPage->CharMax > FontPage->CharMin); FontPageHead = FontPage->FontHead; FontPageTail = FontPage->FontTail; if (FontPageOffsets != NULL) { ASSERT ((UINT8 *)&FontPageOffsets[CurrentPageNumber - FontPageMin] < SavedGlyphOffsets); FontPageOffsets[CurrentPageNumber - FontPageMin] = PageSparseCount; ///< stores sparse index + 1; zeroes mean no page. } FontPage->Glyphs = GlyphData; // // Only use GlyphOffsets table when chars in page are not continuous. // FontPage->GlyphOffsets = ((INTN)PTR_AS_UINTN (FontPage->GlyphOffsets) == (FontPage->CharMax - FontPage->CharMin)) ? NULL : GlyphOffsets; } PageCharMax = PageCharMin + 1; GlyphSparseCount = 0; } else if (PageChar >= PageCharMax) { PageCharMax = PageChar + 1; } else { DEBUG ((DEBUG_WARN, "%a%u hex file must be sorted #%u\n", HEX_FILE_ERROR_PREFIX, LineNumber, 2)); Status = EFI_UNSUPPORTED; break; } ++GlyphSparseCount; if (Pass == 1) { GlyphHead = ISO_CHAR_HEIGHT; GlyphTail = ISO_CHAR_HEIGHT; FoundNonZero = FALSE; for (Line = 0; Line < ISO_CHAR_HEIGHT; Line++) { if (LineGlyphData[Line] != 0) { if (!FoundNonZero) { GlyphHead = Line; FoundNonZero = TRUE; } GlyphTail = ISO_CHAR_HEIGHT - Line - 1; } } if (GlyphHead < FontPageHead) { FontPageHead = GlyphHead; } if (GlyphTail < FontPageTail) { FontPageTail = GlyphTail; } } else { if (FontPage->GlyphOffsets != NULL) { FontPage->GlyphOffsets[PageChar - PageCharMin] = GlyphSparseCount; ///< stores sparse index + 1; zeroes mean no glyph. } CopyMem (GlyphData, &LineGlyphData[FontPageHead], ISO_CHAR_HEIGHT - FontPageHead - FontPageTail); GlyphData += ISO_CHAR_HEIGHT - FontPageHead - FontPageTail; } } ASSERT ((FlexFontPages != NULL) == (Pass == 1)); ASSERT ((*Font != NULL) == (Pass == 2)); if (EFI_ERROR (Status)) { ASSERT (Pass == 1); break; } if (Pass == 1) { FontHeaderSize = sizeof (OC_CONSOLE_FONT); FontPagesSize = PageSparseCount * sizeof (OC_CONSOLE_FONT_PAGE); FontPageOffsetsSize = ((FontPageMax - FontPageMin) == PageSparseCount) ? 0 : ((FontPageMax - FontPageMin) * sizeof (UINT16)); FontSize = FontHeaderSize + FontPagesSize + FontPageOffsetsSize + GlyphOffsetsSize + GlyphDataSize; // // Arranged in decreasing alignment requirement, so no need for padding. // *Font = AllocateZeroPool (FontSize); if (*Font == NULL) { OcFlexArrayFree (&FlexFontPages); return EFI_OUT_OF_RESOURCES; } DEBUG ((DEBUG_INFO, "OCC: Hex font allocated size %u\n", FontSize)); FontPages = (VOID *)((UINT8 *)(*Font) + FontHeaderSize); SavedFontPageOffsets = FontPageOffsets = (VOID *)((UINT8 *)FontPages + FontPagesSize); SavedGlyphOffsets = GlyphOffsets = (VOID *)((UINT8 *)FontPageOffsets + FontPageOffsetsSize); SavedGlyphData = GlyphData = (VOID *)((UINT8 *)GlyphOffsets + GlyphOffsetsSize); SavedEnd = (UINT8 *)GlyphData + GlyphDataSize; if (FontPageOffsetsSize == 0) { FontPageOffsets = NULL; } if (GlyphOffsetsSize == 0) { GlyphOffsets = NULL; } (*Font)->PageMin = FontPageMin; (*Font)->PageMax = FontPageMax; (*Font)->Pages = FontPages; (*Font)->PageOffsets = FontPageOffsets; // // Move flex array items to their permanent home. // ASSERT (FontPagesSize == FlexFontPages->ItemSize * FlexFontPages->Count); CopyMem (FontPages, FlexFontPages->Items, FlexFontPages->ItemSize * FlexFontPages->Count); OcFlexArrayFree (&FlexFontPages); } else { ASSERT ((UINT8 *)FontPages == (UINT8 *)SavedFontPageOffsets); ASSERT (GlyphOffsets == NULL ? (UINT8 *)SavedGlyphOffsets == (UINT8 *)SavedFontPageOffsets : (UINT8 *)GlyphOffsets == (UINT8 *)SavedGlyphData); ASSERT (GlyphData == SavedEnd); } } FreePool (HexFontFile); if (!EFI_ERROR (Status)) { if (!OcConsoleFontContainsChar (*Font, OC_CONSOLE_FONT_FALLBACK_CHAR)) { Status = EFI_UNSUPPORTED; DEBUG ((DEBUG_WARN, "OCC: Hex font does not contain fallback character '%c' - %r\n", OC_CONSOLE_FONT_FALLBACK_CHAR, Status)); } } if (EFI_ERROR (Status)) { if (FlexFontPages != NULL) { OcFlexArrayFree (&FlexFontPages); } if (*Font != NULL) { FreePool (*Font); } } return Status; }