CloverBootloader/rEFIt_UEFI/libeg/text.cpp
2023-11-06 21:53:57 +01:00

433 lines
15 KiB
C++

/*
* libeg/text.c
* Text drawing functions
*
* Copyright (c) 2006 Christoph Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Christoph Pfisterer nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//Slice 2011 - 2016 numerous improvements, 2020 full rewritten for c++
extern "C" {
#include <Protocol/GraphicsOutput.h>
}
#include <Platform.h> // Only use angled for Platform, else, xcode project won't compile
#include "libegint.h"
#include "nanosvg.h"
#include "VectorGraphics.h"
#include "XTheme.h"
#include "../Platform/Settings.h"
#include "../Settings/Self.h"
//#include "egemb_font.h"
//#define FONT_CELL_WIDTH (7)
//#define FONT_CELL_HEIGHT (12)
#ifndef DEBUG_ALL
#define DEBUG_TEXT 0
#else
#define DEBUG_TEXT DEBUG_ALL
#endif
#if DEBUG_TEXT == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_TEXT, __VA_ARGS__)
#endif
const EFI_GRAPHICS_OUTPUT_BLT_PIXEL SemiWhitePixel = {0xFF, 0xFF, 0xFF, 0xD2}; //semitransparent white
NSVGfontChain *fontsDB = NULL;
//
// Text rendering
//
//it is not good for vector theme
//it will be better to sum each letter width for the chosen font
// so one more parameter is TextStyle
void XTheme::MeasureText(IN const XStringW& Text, OUT INTN *Width, OUT INTN *Height)
{
INTN ScaledWidth = CharWidth;
INTN ScaledHeight = FontHeight;
if (Scale != 0.f) {
ScaledWidth = (INTN)(CharWidth * Scale);
ScaledHeight = (INTN)(FontHeight * Scale);
}
if (Width != NULL)
*Width = StrLen(Text.wc_str()) * ((FontWidth > ScaledWidth) ? FontWidth : ScaledWidth);
if (Height != NULL)
*Height = ScaledHeight;
}
void XTheme::LoadFontImage(IN XBool UseEmbedded, IN INTN Rows, IN INTN Cols)
{
EFI_STATUS Status = EFI_NOT_FOUND;
XImage NewImage; //tempopary image from file
INTN ImageWidth, ImageHeight;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *PixelPtr;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *FontPtr;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL FirstPixel;
XBool isKorean = (gSettings.GUI.languageCode == korean);
XStringW fontFilePath;
const XStringW& commonFontDir = L"font"_XSW;
if (IsEmbeddedTheme() && !isKorean) { //or initial screen before theme init
Status = NewImage.FromPNG(ACCESS_EMB_DATA(emb_font_data), ACCESS_EMB_SIZE(emb_font_data)); //always success
MsgLog("Using embedded font\n");
} else if (isKorean){
Status = NewImage.LoadXImage(ThemeDir, L"FontKorean.png"_XSW);
MsgLog("Loading korean font from ThemeDir: %s\n", efiStrError(Status));
if (!EFI_ERROR(Status)) {
CharWidth = 22; //standard for korean
} else {
MsgLog("...using english\n");
gSettings.GUI.languageCode = english;
}
}
if (EFI_ERROR(Status)) {
//not loaded, use common
Rows = 16; //standard for english
Cols = 16;
Status = NewImage.LoadXImage(ThemeDir, FontFileName);
}
if (EFI_ERROR(Status)) {
//then take from common font folder
// fontFilePath = SWPrintf(L"%s\\%s", commonFontDir, isKorean ? L"FontKorean.png" : ThemeX->FontFileName.data());
fontFilePath = commonFontDir + FontFileName;
Status = NewImage.LoadXImage(&self.getCloverDir(), fontFilePath);
//else use embedded even if it is not embedded
if (EFI_ERROR(Status)) {
Status = NewImage.FromPNG(ACCESS_EMB_DATA(emb_font_data), ACCESS_EMB_SIZE(emb_font_data));
}
if (EFI_ERROR(Status)) {
MsgLog("No font found!\n");
return;
}
}
ImageWidth = NewImage.GetWidth();
// DBG("ImageWidth=%lld\n", ImageWidth);
ImageHeight = NewImage.GetHeight();
// DBG("ImageHeight=%lld\n", ImageHeight);
PixelPtr = NewImage.GetPixelPtr(0,0);
FontImage.setSizeInPixels(ImageWidth * Rows, ImageHeight / Rows);
FontPtr = FontImage.GetPixelPtr(0,0);
FontWidth = ImageWidth / Cols;
FontHeight = ImageHeight / Rows;
TextHeight = FontHeight + (int)(TEXT_YMARGIN * 2 * Scale);
// if (!isKorean) {
// CharWidth = FontWidth; //there is default value anyway
// }
FirstPixel = *PixelPtr;
for (INTN y = 0; y < Rows; y++) {
for (INTN j = 0; j < FontHeight; j++) {
INTN Ypos = ((j * Rows) + y) * ImageWidth;
for (INTN x = 0; x < ImageWidth; x++) {
if (
//First pixel is accounting as "blue key"
(PixelPtr->Blue == FirstPixel.Blue) &&
(PixelPtr->Green == FirstPixel.Green) &&
(PixelPtr->Red == FirstPixel.Red)
) {
PixelPtr->Reserved = 0; //if a pixel has same color as first pixel then it will be transparent
//} else if (ThemeX->DarkEmbedded) {
} else if (ThemeX->embedded && !ThemeX->Daylight) {
*PixelPtr = SemiWhitePixel; //special case to change a text to semi white, not blue pixels
}
FontPtr[Ypos + x] = *PixelPtr++; //not (x, YPos) !!!
}
}
}
}
void XTheme::PrepareFont()
{
TextHeight = FontHeight + (int)(TEXT_YMARGIN * 2 * Scale);
if (TypeSVG) {
return;
}
// load the font
if (FontImage.isEmpty()) {
DBG("load font image type %d\n", Font);
LoadFontImage(true, 16, 16); //anyway success
}
if (!FontImage.isEmpty()) {
if (Font == FONT_GRAY) {
//invert the font. embedded is dark
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p = FontImage.GetPixelPtr(0,0);
for (INTN Height = 0; Height < FontImage.GetHeight(); Height++){
for (INTN Width = 0; Width < FontImage.GetWidth(); Width++, p++){
p->Blue ^= 0xFF;
p->Green ^= 0xFF;
p->Red ^= 0xFF;
//p->a = 0xFF; //huh! dont invert opacity
}
}
// FontImage.Draw(0, 300, 0.6f); //for debug purpose
}
DBG("Font %d prepared WxH=%lldx%lld CharWidth=%lld\n", Font, FontWidth, FontHeight, CharWidth);
} else {
DBG("Failed to load font\n");
}
}
//search pixel similar to first
inline XBool SamePix(const EFI_GRAPHICS_OUTPUT_BLT_PIXEL& Ptr, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL& FirstPixel)
{
//compare with first pixel of the array top-left point [0][0]
return ((Ptr.Red >= FirstPixel.Red - (FirstPixel.Red >> 2)) &&
(Ptr.Red <= FirstPixel.Red + (FirstPixel.Red >> 2)) &&
(Ptr.Green >= FirstPixel.Green - (FirstPixel.Green >> 2)) &&
(Ptr.Green <= FirstPixel.Green + (FirstPixel.Green >> 2)) &&
(Ptr.Blue >= FirstPixel.Blue - (FirstPixel.Blue >> 2)) &&
(Ptr.Blue <= FirstPixel.Blue + (FirstPixel.Blue >> 2)) &&
(Ptr.Reserved == FirstPixel.Reserved)); //hack for transparent fonts
}
//used for proportional fonts in raster themes
//search empty column from begin Step=1 or from end Step=-1 in the input buffer
// empty means similar to FirstPixel
INTN XTheme::GetEmpty(const XImage& Buffer, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL& FirstPixel, INTN MaxWidth, INTN Start, INTN Step)
{
INTN m, i;
// INTN Shift = (Step > 0)?0:1;
m = MaxWidth;
if (Step == 1) {
for (INTN j = 0; j < FontHeight; j++) {
for (i = 0; i < MaxWidth; i++) {
if (!SamePix(Buffer.GetPixel(Start + i,j), FirstPixel)) { //found not empty pixel
break;
}
}
m = MIN(m, i); //for each line to find minimum
if (m == 0) break;
}
} else { // go back
for (INTN j = 0; j < FontHeight; j++) {
for (i = 1; i <= MaxWidth; i++) {
if (!SamePix(Buffer.GetPixel(Start - i,j), FirstPixel)) { //found not empty pixel
break;
}
}
m = MIN(m, i); //for each line to find minimum
if (m == 0) break;
}
}
return m;
}
INTN XTheme::RenderText(IN const XString8& Text, OUT XImage* CompImage_ptr,
IN INTN PosX, IN INTN PosY, IN UINTN Cursor, INTN textType, float textScale)
{
const XStringW UTF16Text = XStringW().takeValueFrom(Text.c_str());
return RenderText(UTF16Text, CompImage_ptr, PosX, PosY, Cursor, textType, textScale);
}
INTN XTheme::RenderText(IN const XStringW& Text, OUT XImage* CompImage_ptr,
IN INTN PosX, IN INTN PosY, IN UINTN Cursor, INTN textType, float textScale)
{
XImage& CompImage = *CompImage_ptr;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL FontPixel;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL FirstPixel;
UINTN TextLength;
UINTN Cho = 0, Jong = 0, Joong = 0;
INTN LeftSpace, RightSpace;
if (TypeSVG) {
return renderSVGtext(&CompImage, PosX, PosY, textType, Text, Cursor);
}
if (textScale == 0.f) {
textScale = 1.f;
}
INTN CharScaledWidth = (INTN)(CharWidth * textScale);
// clip the text
// TextLength = StrLenInWChar(Text.wc_str()); //it must be UTF16 length
TextLength = StrLen(Text.wc_str());
DBG("text to render %ls length %lld\n", Text.wc_str(), TextLength);
if (FontImage.isEmpty()) {
PrepareFont(); //at the boot screen there is embedded font
}
DBG("TextLength =%lld PosX=%lld PosY=%lld\n", TextLength, PosX, PosY);
FirstPixel = CompImage.GetPixel(0,0);
FontPixel = FontImage.GetPixel(0,0);
UINT16 c0 = 0x20;
INTN RealWidth = CharScaledWidth;
INTN Shift = INTN((FontWidth - CharWidth) * textScale / 2); // cast to INTN to avoid warning
if (Shift < 0) {
Shift = 0;
}
DBG("FontWidth=%lld, CharWidth=%lld\n", FontWidth, RealWidth);
EG_RECT Area; //area is scaled
Area.YPos = PosY; // not sure
Area.Height = TextHeight;
EG_RECT Bukva; //bukva is not scaled place
Bukva.YPos = 0;
Bukva.Width = FontWidth;
Bukva.Height = FontHeight;
DBG("codepage=%llx, asciiPage=%x\n", GlobalConfig.Codepage, AsciiPageSize);
for (UINTN i = 0; i < TextLength && c0 != 0; i++) {
UINT16 c = Text.wc_str()[i]; //including UTF8 -> UTF16 conversion
DBG("initial char to render 0x%hx\n", c); //good
if (gSettings.GUI.languageCode != korean) { //russian Codepage = 0x410
if (c >= 0x410 && c < 0x450) {
//we have russian raster fonts with chars at 0xC0
c -= 0x350;
} else {
INTN c2 = (c >= GlobalConfig.Codepage) ? (c - GlobalConfig.Codepage + AsciiPageSize) : c; //International letters
c = c2 & 0xFF; //this maximum raster font size
}
// DBG("char to render 0x%hhx\n", c);
if (Proportional) {
//find spaces {---comp--__left__|__right__--char---}
if (c0 <= 0x20) { // space before or at buffer edge
LeftSpace = 2;
} else {
LeftSpace = GetEmpty(CompImage, FirstPixel, RealWidth, PosX, -1);
}
if (c <= 0x20) { //new space will be half font width
RightSpace = 1;
RealWidth = (CharScaledWidth >> 1) + 1;
} else {
RightSpace = GetEmpty(FontImage, FontPixel, FontWidth, c * FontWidth, 1); //not scaled yet
if (RightSpace >= FontWidth) {
RightSpace = 0; //empty place for invisible characters
}
//RealWidth = CharScaledWidth - (int)(RightSpace * textScale); //a part of char
RealWidth = (__typeof__(RealWidth))((FontWidth - RightSpace) * textScale); //a part of char
}
} else {
LeftSpace = 2;
RightSpace = Shift;
}
LeftSpace = (int)(LeftSpace * textScale); //was not scaled yet
//RightSpace will not be scaled
// RealWidth are scaled now
DBG(" RealWidth = %lld LeftSpace = %lld RightSpace = %lld\n", RealWidth, LeftSpace, RightSpace);
c0 = c; //remember old value
if (PosX + RealWidth > CompImage.GetWidth()) {
DBG("no more place for character\n");
break;
}
Area.XPos = PosX + 2 - LeftSpace;
Area.Width = RealWidth;
Bukva.XPos = c * FontWidth + RightSpace;
DBG("place [%lld,%lld,%lld,%lld], bukva [%lld,%lld,%lld,%lld]\n",
Area.XPos, Area.YPos, Area.Width, Area.Height,
Bukva.XPos, Bukva.YPos, Bukva.Width, Bukva.Height);
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
// CompImage.CopyRect(FontImage, Area, Bukva);
if (i == Cursor) {
c = 0x5F;
Bukva.XPos = c * FontWidth + RightSpace;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
}
PosX += RealWidth - LeftSpace + 2; //next char position
} else {
//
//Slice - I am not sure in any of this digits
//someone knowning korean should revise this
//
UINT16 c1 = c;
if ((c >= 0x20) && (c <= 0x7F)) {
c1 = ((c - 0x20) >> 4) * 28 + (c & 0x0F);
Cho = c1;
Shift = 12;
} else if ((c < 0x20) || ((c > 0x7F) && (c < 0xAC00))) {
Cho = 0x0E; //just a dot
Shift = 8;
} else if ((c >= 0xAC00) && (c <= 0xD638)) {
//korean
Shift = 18;
c -= 0xAC00;
c1 = c / 28;
Jong = c % 28;
Cho = c1 / 21;
Joong = c1 % 21;
Cho += 28 * 7;
Joong += 28 * 8;
Jong += 28 * 9;
}
Area.XPos = PosX;
Area.Width = CharWidth;
// DBG("Cho=%d Joong=%d Jong=%d\n", Cho, Joong, Jong);
if (Shift == 18) {
Bukva.XPos = Cho * FontWidth + 4;
Bukva.YPos = 1;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
} else {
Area.YPos = PosY + 3;
Bukva.XPos = Cho * FontWidth + 2;
Bukva.YPos = 0;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
}
if (i == Cursor) {
c = 99;
Bukva.XPos = c * FontWidth + 2;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
}
if (Shift == 18) {
Area.XPos = PosX + 9;
Area.YPos = PosY;
Bukva.XPos = Joong * FontWidth + 6;
Bukva.YPos = 0;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
Area.XPos = PosX;
Area.YPos = PosY + 9;
Bukva.XPos = Jong * FontWidth + 1;
CompImage.Compose(Area, Bukva, FontImage, false, textScale);
}
PosX += CharWidth; //Shift;
}
}
return PosX;
}
/* EOF */