/* * font.c: Font handling for the DVB On Screen Display * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * BiDi support by Osama Alrawab @2008 Tripoli-Libya. * * $Id: font.c 5.2 2022/12/06 12:30:13 kls Exp $ */ #include "font.h" #include #include #ifdef BIDI #include #endif #include #include FT_FREETYPE_H #include "config.h" #include "osd.h" #include "tools.h" const char *DefaultFontOsd = "Sans Serif:Bold"; const char *DefaultFontSml = "Sans Serif"; const char *DefaultFontFix = "Courier:Bold"; // --- cFreetypeFont --------------------------------------------------------- #define KERNING_UNKNOWN (-10000) struct tKerning { uint prevSym; int kerning; tKerning(uint PrevSym, int Kerning = 0) { prevSym = PrevSym; kerning = Kerning; } }; class cGlyph : public cListObject { private: uint charCode; uchar *bitmap; int advanceX; int advanceY; int left; ///< The bitmap's left bearing expressed in integer pixels. int top; ///< The bitmap's top bearing expressed in integer pixels. int width; ///< The number of pixels per bitmap row. int rows; ///< The number of bitmap rows. int pitch; ///< The pitch's absolute value is the number of bytes taken by one bitmap row, including padding. cVector kerningCache; public: cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData); virtual ~cGlyph(); uint CharCode(void) const { return charCode; } uchar *Bitmap(void) const { return bitmap; } int AdvanceX(void) const { return advanceX; } int AdvanceY(void) const { return advanceY; } int Left(void) const { return left; } int Top(void) const { return top; } int Width(void) const { return width; } int Rows(void) const { return rows; } int Pitch(void) const { return pitch; } int GetKerningCache(uint PrevSym) const; void SetKerningCache(uint PrevSym, int Kerning); }; cGlyph::cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData) { charCode = CharCode; advanceX = GlyphData->advance.x >> 6; advanceY = GlyphData->advance.y >> 6; left = GlyphData->bitmap_left; top = GlyphData->bitmap_top; width = GlyphData->bitmap.width; rows = GlyphData->bitmap.rows; pitch = GlyphData->bitmap.pitch; bitmap = MALLOC(uchar, rows * pitch); if (int bytes = rows * pitch) memcpy(bitmap, GlyphData->bitmap.buffer, bytes); } cGlyph::~cGlyph() { free(bitmap); } int cGlyph::GetKerningCache(uint PrevSym) const { for (int i = kerningCache.Size(); --i > 0; ) { if (kerningCache[i].prevSym == PrevSym) return kerningCache[i].kerning; } return KERNING_UNKNOWN; } void cGlyph::SetKerningCache(uint PrevSym, int Kerning) { kerningCache.Append(tKerning(PrevSym, Kerning)); } class cFreetypeFont : public cFont { private: cString fontName; int size; int width; int height; int bottom; FT_Library library; ///< Handle to library FT_Face face; ///< Handle to face object mutable cList glyphCacheMonochrome; mutable cList glyphCacheAntiAliased; int Bottom(void) const { return bottom; } int Kerning(cGlyph *Glyph, uint PrevSym) const; cGlyph* Glyph(uint CharCode, bool AntiAliased = false) const; public: cFreetypeFont(const char *Name, int CharHeight, int CharWidth = 0); virtual ~cFreetypeFont(); virtual const char *FontName(void) const { return fontName; } virtual int Size(void) const { return size; } virtual int Width(void) const { return width; } virtual int Width(uint c) const; virtual int Width(const char *s) const; virtual int Height(void) const { return height; } virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const; virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const; }; cFreetypeFont::cFreetypeFont(const char *Name, int CharHeight, int CharWidth) { fontName = Name; size = CharHeight; width = CharWidth; height = 0; bottom = 0; int error = FT_Init_FreeType(&library); if (!error) { error = FT_New_Face(library, Name, 0, &face); if (!error) { if (face->num_fixed_sizes && face->available_sizes) { // fixed font // TODO what exactly does all this mean? height = face->available_sizes->height; for (uint sym ='A'; sym < 'z'; sym++) { // search for descender for fixed font FIXME FT_UInt glyph_index = FT_Get_Char_Index(face, sym); error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if (!error) { error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); if (!error) { if (int(face->glyph->bitmap.rows-face->glyph->bitmap_top) > bottom) bottom = face->glyph->bitmap.rows-face->glyph->bitmap_top; } else esyslog("ERROR: FreeType: error %d in FT_Render_Glyph", error); } else esyslog("ERROR: FreeType: error %d in FT_Load_Glyph", error); } } else { error = FT_Set_Char_Size(face, // handle to face object CharWidth * 64, // CharWidth in 1/64th of points CharHeight * 64, // CharHeight in 1/64th of points 0, // horizontal device resolution 0); // vertical device resolution if (!error) { height = (face->size->metrics.ascender - face->size->metrics.descender + 63) / 64; bottom = abs((face->size->metrics.descender - 63) / 64); } else esyslog("ERROR: FreeType: error %d during FT_Set_Char_Size (font = %s)\n", error, Name); } } else esyslog("ERROR: FreeType: load error %d (font = %s)", error, Name); } else esyslog("ERROR: FreeType: initialization error %d (font = %s)", error, Name); } cFreetypeFont::~cFreetypeFont() { FT_Done_Face(face); FT_Done_FreeType(library); } int cFreetypeFont::Kerning(cGlyph *Glyph, uint PrevSym) const { int kerning = 0; if (Glyph && PrevSym) { kerning = Glyph->GetKerningCache(PrevSym); if (kerning == KERNING_UNKNOWN) { FT_Vector delta; FT_UInt glyph_index = FT_Get_Char_Index(face, Glyph->CharCode()); FT_UInt glyph_index_prev = FT_Get_Char_Index(face, PrevSym); FT_Get_Kerning(face, glyph_index_prev, glyph_index, FT_KERNING_DEFAULT, &delta); kerning = delta.x / 64; Glyph->SetKerningCache(PrevSym, kerning); } } return kerning; } cGlyph* cFreetypeFont::Glyph(uint CharCode, bool AntiAliased) const { // Non-breaking space: if (CharCode == 0xA0) CharCode = 0x20; // Lookup in cache: cList *glyphCache = AntiAliased ? &glyphCacheAntiAliased : &glyphCacheMonochrome; for (cGlyph *g = glyphCache->First(); g; g = glyphCache->Next(g)) { if (g->CharCode() == CharCode) return g; } FT_UInt glyph_index = FT_Get_Char_Index(face, CharCode); // Load glyph image into the slot (erase previous one): int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); if (error) esyslog("ERROR: FreeType: error during FT_Load_Glyph"); else { #if ((FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 7) || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 2 && FREETYPE_PATCH <= 1))// TODO workaround for bug? which one? if (AntiAliased || CharCode == 32) #else if (AntiAliased) #endif error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); else error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO); if (error) esyslog("ERROR: FreeType: error during FT_Render_Glyph %d, %d\n", CharCode, glyph_index); else { //new bitmap cGlyph *Glyph = new cGlyph(CharCode, face->glyph); glyphCache->Add(Glyph); return Glyph; } } #define UNKNOWN_GLYPH_INDICATOR '?' if (CharCode != UNKNOWN_GLYPH_INDICATOR) return Glyph(UNKNOWN_GLYPH_INDICATOR, AntiAliased); return NULL; } int cFreetypeFont::Width(uint c) const { cGlyph *g = Glyph(c, Setup.AntiAlias); return g ? g->AdvanceX() : 0; } int cFreetypeFont::Width(const char *s) const { int w = 0; if (s) { #ifdef BIDI cString bs = Bidi(s); s = bs; #endif uint prevSym = 0; while (*s) { int sl = Utf8CharLen(s); uint sym = Utf8CharGet(s, sl); s += sl; cGlyph *g = Glyph(sym, Setup.AntiAlias); if (g) w += g->AdvanceX() + Kerning(g, prevSym); prevSym = sym; } } return w; } #define MAX_BLEND_LEVELS 256 void cFreetypeFont::DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const { if (s && height) { // checking height to make sure we actually have a valid font #ifdef BIDI cString bs = Bidi(s); s = bs; #endif bool AntiAliased = Setup.AntiAlias && Bitmap->Bpp() >= 8; bool TransparentBackground = ColorBg == clrTransparent; int16_t BlendLevelIndex[MAX_BLEND_LEVELS]; // tIndex is 8 bit unsigned, so a negative value can be used to mark unused entries if (AntiAliased && !TransparentBackground) memset(BlendLevelIndex, 0xFF, sizeof(BlendLevelIndex)); // initializes the array with negative values tIndex fg = Bitmap->Index(ColorFg); uint prevSym = 0; while (*s) { int sl = Utf8CharLen(s); uint sym = Utf8CharGet(s, sl); s += sl; cGlyph *g = Glyph(sym, AntiAliased); if (!g) continue; int kerning = Kerning(g, prevSym); prevSym = sym; uchar *buffer = g->Bitmap(); int symWidth = g->Width(); if (Width && x + symWidth + g->Left() + kerning - 1 > Width) break; // we don't draw partial characters if (x + symWidth + g->Left() + kerning > 0) { for (int row = 0; row < g->Rows(); row++) { for (int pitch = 0; pitch < g->Pitch(); pitch++) { uchar bt = *(buffer + (row * g->Pitch() + pitch)); if (AntiAliased) { if (bt > 0x00) { int px = x + pitch + g->Left() + kerning; int py = y + row + (height - Bottom() - g->Top()); tColor bg; if (bt == 0xFF) bg = fg; else if (TransparentBackground) bg = Bitmap->Index(Bitmap->Blend(ColorFg, Bitmap->GetColor(px, py), bt)); else if (BlendLevelIndex[bt] >= 0) bg = BlendLevelIndex[bt]; else bg = BlendLevelIndex[bt] = Bitmap->Index(Bitmap->Blend(ColorFg, ColorBg, bt)); Bitmap->SetIndex(px, py, bg); } } else { //monochrome rendering for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) { if (bt & 0x80) Bitmap->SetIndex(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top()), fg); bt <<= 1; } } } } } x += g->AdvanceX() + kerning; if (x > Bitmap->Width() - 1) break; } } } void cFreetypeFont::DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const { if (s && height) { // checking height to make sure we actually have a valid font #ifdef BIDI cString bs = Bidi(s); s = bs; #endif bool AntiAliased = Setup.AntiAlias; uint prevSym = 0; while (*s) { int sl = Utf8CharLen(s); uint sym = Utf8CharGet(s, sl); s += sl; cGlyph *g = Glyph(sym, AntiAliased); if (!g) continue; int kerning = Kerning(g, prevSym); prevSym = sym; uchar *buffer = g->Bitmap(); int symWidth = g->Width(); if (Width && x + symWidth + g->Left() + kerning - 1 > Width) break; // we don't draw partial characters if (x + symWidth + g->Left() + kerning > 0) { for (int row = 0; row < g->Rows(); row++) { for (int pitch = 0; pitch < g->Pitch(); pitch++) { uchar bt = *(buffer + (row * g->Pitch() + pitch)); if (AntiAliased) { if (bt > 0x00) Pixmap->DrawPixel(cPoint(x + pitch + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), AlphaBlend(ColorFg, ColorBg, bt)); } else { //monochrome rendering for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) { if (bt & 0x80) Pixmap->DrawPixel(cPoint(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), ColorFg); bt <<= 1; } } } } } x += g->AdvanceX() + kerning; if (x > Pixmap->DrawPort().Width() - 1) break; } } } // --- cDummyFont ------------------------------------------------------------ // A dummy font, in case there are no fonts installed: class cDummyFont : public cFont { private: int height; int width; public: cDummyFont(int CharHeight, int CharWidth) { height = CharHeight; width = CharWidth; } virtual int Width(void) const { return width ? width : height; } virtual int Width(uint c) const { return width ? width : height; } virtual int Width(const char *s) const { return width ? width : height; } virtual int Height(void) const { return height; } virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {} virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {}; }; // --- cFont ----------------------------------------------------------------- cFont *cFont::fonts[eDvbFontSize] = { NULL }; void cFont::SetFont(eDvbFont Font, const char *Name, int CharHeight) { delete fonts[Font]; fonts[Font] = CreateFont(Name, constrain(CharHeight, MINFONTSIZE, MAXFONTSIZE)); } const cFont *cFont::GetFont(eDvbFont Font) { if (Setup.UseSmallFont == 0 && Font == fontSml) Font = fontOsd; else if (Setup.UseSmallFont == 2) Font = fontSml; if (!fonts[Font]) { switch (Font) { case fontOsd: SetFont(Font, Setup.FontOsd, Setup.FontOsdSize); break; case fontSml: SetFont(Font, Setup.FontSml, min(Setup.FontSmlSize, Setup.FontOsdSize)); break; case fontFix: SetFont(Font, Setup.FontFix, Setup.FontFixSize); break; default: esyslog("ERROR: unknown Font %d (%s %d)", Font, __FUNCTION__, __LINE__); } } return fonts[Font]; } cFont *cFont::CreateFont(const char *Name, int CharHeight, int CharWidth) { cString fn = GetFontFileName(Name); cFont *f = *fn ? new cFreetypeFont(fn, CharHeight, CharWidth) : NULL; if (!f || !f->Height()) { delete f; f = new cDummyFont(CharHeight, CharWidth); } return f; } bool cFont::GetAvailableFontNames(cStringList *FontNames, bool Monospaced) { if (!FontNames->Size()) { FcInit(); FcObjectSet *os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL); FcPattern *pat = FcPatternCreate(); FcPatternAddBool(pat, FC_SCALABLE, FcTrue); if (Monospaced) FcPatternAddInteger(pat, FC_SPACING, FC_MONO); FcFontSet* fontset = FcFontList(NULL, pat, os); for (int i = 0; i < fontset->nfont; i++) { char *s = (char *)FcNameUnparse(fontset->fonts[i]); if (s) { // Strip i18n stuff: char *c = strchr(s, ':'); if (c) { char *p = strchr(c + 1, ','); if (p) *p = 0; } char *p = strchr(s, ','); if (p) { if (c) memmove(p, c, strlen(c) + 1); else *p = 0; } // Make it user presentable: s = strreplace(s, "\\", ""); // '-' is escaped s = strreplace(s, "style=", ""); FontNames->Append(s); // takes ownership of s } } FcFontSetDestroy(fontset); FcPatternDestroy(pat); FcObjectSetDestroy(os); //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once FontNames->Sort(); } return FontNames->Size() > 0; } cString cFont::GetFontFileName(const char *FontName) { cString FontFileName; if (FontName) { char *fn = strdup(FontName); fn = strreplace(fn, ":", ":style="); fn = strreplace(fn, "-", "\\-"); FcInit(); FcPattern *pat = FcNameParse((FcChar8 *)fn); FcPatternAddBool(pat, FC_SCALABLE, FcTrue); FcConfigSubstitute(NULL, pat, FcMatchPattern); FcDefaultSubstitute(pat); FcResult fresult; FcFontSet *fontset = FcFontSort(NULL, pat, FcFalse, NULL, &fresult); if (fontset) { for (int i = 0; i < fontset->nfont; i++) { FcBool scalable; FcPatternGetBool(fontset->fonts[i], FC_SCALABLE, 0, &scalable); if (scalable) { FcChar8 *s = NULL; FcPatternGetString(fontset->fonts[i], FC_FILE, 0, &s); FontFileName = (char *)s; break; } } FcFontSetDestroy(fontset); } else esyslog("ERROR: no usable font found for '%s'", FontName); FcPatternDestroy(pat); free(fn); //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once } return FontFileName; } #ifdef BIDI cString cFont::Bidi(const char *Ltr) { if (!cCharSetConv::SystemCharacterTable()) { // bidi requires UTF-8 fribidi_set_mirroring(true); fribidi_set_reorder_nsm(false); FriBidiCharSet fribidiCharset = FRIBIDI_CHAR_SET_UTF8; int LtrLen = strlen(Ltr); FriBidiCharType Base = FRIBIDI_TYPE_L; FriBidiChar *Logical = MALLOC(FriBidiChar, LtrLen + 1) ; int RtlLen = fribidi_charset_to_unicode(fribidiCharset, const_cast(Ltr), LtrLen, Logical); FriBidiChar *Visual = MALLOC(FriBidiChar, LtrLen + 1) ; char *Rtl = NULL; bool ok = fribidi_log2vis(Logical, RtlLen, &Base, Visual, NULL, NULL, NULL); if (ok) { fribidi_remove_bidi_marks(Visual, RtlLen, NULL, NULL, NULL); Rtl = MALLOC(char, RtlLen * 4 + 1); fribidi_unicode_to_charset(fribidiCharset, Visual, RtlLen, Rtl); } free(Logical); free(Visual); if (ok) return cString(Rtl, true); } return cString(Ltr); } #endif // --- cTextWrapper ---------------------------------------------------------- cTextWrapper::cTextWrapper(void) { text = eol = NULL; lines = 0; lastLine = -1; } cTextWrapper::cTextWrapper(const char *Text, const cFont *Font, int Width) { text = NULL; Set(Text, Font, Width); } cTextWrapper::~cTextWrapper() { free(text); } void cTextWrapper::Set(const char *Text, const cFont *Font, int Width) { free(text); text = Text ? strdup(Text) : NULL; eol = NULL; lines = 0; lastLine = -1; if (!text) return; lines = 1; if (Width <= 0) return; char *Blank = NULL; char *Delim = NULL; int w = 0; stripspace(text); // strips trailing newlines for (char *p = text; *p; ) { int sl = Utf8CharLen(p); uint sym = Utf8CharGet(p, sl); if (sym == '\n') { lines++; w = 0; Blank = Delim = NULL; p++; continue; } else if (sl == 1 && isspace(sym)) Blank = p; int cw = Font->Width(sym); if (w + cw > Width) { if (Blank) { *Blank = '\n'; p = Blank; continue; } else if (w > 0) { // there has to be at least one character before the newline // Here's the ugly part, where we don't have any whitespace to // punch in a newline, so we need to make room for it: if (Delim) p = Delim + 1; // let's fall back to the most recent delimiter char *s = MALLOC(char, strlen(text) + 2); // The additional '\n' plus the terminating '\0' int l = p - text; strncpy(s, text, l); s[l] = '\n'; strcpy(s + l + 1, p); free(text); text = s; p = text + l; continue; } } w += cw; if (strchr("-.,:;!?_", *p)) { Delim = p; Blank = NULL; } p += sl; } } const char *cTextWrapper::Text(void) { if (eol) { *eol = '\n'; eol = NULL; } return text; } const char *cTextWrapper::GetLine(int Line) { char *s = NULL; if (Line < lines) { if (eol) { *eol = '\n'; if (Line == lastLine + 1) s = eol + 1; eol = NULL; } if (!s) { s = text; for (int i = 0; i < Line; i++) { s = strchr(s, '\n'); if (s) s++; else break; } } if (s) { if ((eol = strchr(s, '\n')) != NULL) *eol = 0; } lastLine = Line; } return s; }