mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
Freetype font support; full UTF-8 support; dropped pixel fonts
This commit is contained in:
368
font.c
368
font.c
@@ -4,125 +4,296 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: font.c 1.14 2007/03/11 09:51:44 kls Exp $
|
||||
* $Id: font.c 1.15 2007/06/09 14:41:27 kls Exp $
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include "font.h"
|
||||
#include <ctype.h>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "config.h"
|
||||
#include "osd.h"
|
||||
#include "tools.h"
|
||||
|
||||
#include "fontfix-iso8859-1.c"
|
||||
#include "fontosd-iso8859-1.c"
|
||||
#include "fontsml-iso8859-1.c"
|
||||
// --- cFreetypeFont ---------------------------------------------------------
|
||||
|
||||
#include "fontfix-iso8859-2.c"
|
||||
#include "fontosd-iso8859-2.c"
|
||||
#include "fontsml-iso8859-2.c"
|
||||
#define KERNING_UNKNOWN (-10000)
|
||||
|
||||
#include "fontfix-iso8859-5.c"
|
||||
#include "fontosd-iso8859-5.c"
|
||||
#include "fontsml-iso8859-5.c"
|
||||
|
||||
#include "fontfix-iso8859-7.c"
|
||||
#include "fontosd-iso8859-7.c"
|
||||
#include "fontsml-iso8859-7.c"
|
||||
|
||||
#include "fontfix-iso8859-9.c"
|
||||
#include "fontosd-iso8859-9.c"
|
||||
#include "fontsml-iso8859-9.c"
|
||||
|
||||
#include "fontfix-iso8859-13.c"
|
||||
#include "fontosd-iso8859-13.c"
|
||||
#include "fontsml-iso8859-13.c"
|
||||
|
||||
#include "fontfix-iso8859-15.c"
|
||||
#include "fontosd-iso8859-15.c"
|
||||
#include "fontsml-iso8859-15.c"
|
||||
|
||||
// --- cFont -----------------------------------------------------------------
|
||||
|
||||
static const void *const FontData[eDvbCodeSize][eDvbFontSize] = {
|
||||
{ FontOsd_iso8859_1, FontFix_iso8859_1, FontSml_iso8859_1 },
|
||||
{ FontOsd_iso8859_2, FontFix_iso8859_2, FontSml_iso8859_2 },
|
||||
{ FontOsd_iso8859_5, FontFix_iso8859_5, FontSml_iso8859_5 },
|
||||
{ FontOsd_iso8859_7, FontFix_iso8859_7, FontSml_iso8859_7 },
|
||||
{ FontOsd_iso8859_9, FontFix_iso8859_9, FontSml_iso8859_9 },
|
||||
{ FontOsd_iso8859_13, FontFix_iso8859_13, FontSml_iso8859_13 },
|
||||
{ FontOsd_iso8859_15, FontFix_iso8859_15, FontSml_iso8859_15 },
|
||||
struct tKerning {
|
||||
uint prevSym;
|
||||
int kerning;
|
||||
tKerning(uint PrevSym, int Kerning) { prevSym = PrevSym; kerning = Kerning; }
|
||||
};
|
||||
|
||||
static const char *FontCode[eDvbCodeSize] = {
|
||||
"iso8859-1",
|
||||
"iso8859-2",
|
||||
"iso8859-5",
|
||||
"iso8859-7",
|
||||
"iso8859-9",
|
||||
"iso8859-13",
|
||||
"iso8859-15",
|
||||
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<tKerning> 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);
|
||||
};
|
||||
|
||||
eDvbCode cFont::code = code_iso8859_1;
|
||||
cFont *cFont::fonts[eDvbFontSize] = { NULL };
|
||||
|
||||
cFont::cFont(const void *Data)
|
||||
cGlyph::cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData)
|
||||
{
|
||||
SetData(Data);
|
||||
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);
|
||||
memcpy(bitmap, GlyphData->bitmap.buffer, rows * pitch);
|
||||
}
|
||||
|
||||
void cFont::SetData(const void *Data)
|
||||
cGlyph::~cGlyph()
|
||||
{
|
||||
if (Data) {
|
||||
height = ((tCharData *)Data)->height;
|
||||
for (int i = 0; i < NUMCHARS; i++)
|
||||
data[i] = (tCharData *)&((tPixelData *)Data)[(i < 32 ? 0 : i - 32) * (height + 2)];
|
||||
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:
|
||||
int height;
|
||||
int bottom;
|
||||
FT_Library library; ///< Handle to library
|
||||
FT_Face face; ///< Handle to face object
|
||||
mutable cList<cGlyph> glyphCacheMonochrome;
|
||||
mutable cList<cGlyph> 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);
|
||||
virtual ~cFreetypeFont();
|
||||
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;
|
||||
};
|
||||
|
||||
cFreetypeFont::cFreetypeFont(const char *Name, int CharHeight)
|
||||
{
|
||||
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 (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
|
||||
0, // char_width 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
|
||||
height = 0;
|
||||
esyslog("ERROR: FreeType: initialization error %d (font = %s)", error, Name);
|
||||
}
|
||||
|
||||
int cFont::Width(const char *s) const
|
||||
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
|
||||
{
|
||||
// Lookup in cache:
|
||||
cList<cGlyph> *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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
while (s && *s)
|
||||
w += Width(*s++);
|
||||
if (s) {
|
||||
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;
|
||||
}
|
||||
|
||||
int cFont::Height(const char *s) const
|
||||
void cFreetypeFont::DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const
|
||||
{
|
||||
int h = 0;
|
||||
if (s && *s)
|
||||
h = height; // all characters have the same height!
|
||||
return h;
|
||||
}
|
||||
|
||||
bool cFont::SetCode(const char *Code)
|
||||
{
|
||||
for (int i = 0; i < eDvbCodeSize; i++) {
|
||||
if (strcmp(Code, FontCode[i]) == 0) {
|
||||
SetCode(eDvbCode(i));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cFont::SetCode(eDvbCode Code)
|
||||
{
|
||||
if (code != Code) {
|
||||
code = Code;
|
||||
for (int i = 0; i < eDvbFontSize; i++) {
|
||||
if (fonts[i])
|
||||
fonts[i]->SetData(FontData[code][i]);
|
||||
}
|
||||
if (s && height) { // checking height to make sure we actually have a valid font
|
||||
bool AntiAliased = Setup.AntiAlias && Bitmap->Bpp() >= 8;
|
||||
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);
|
||||
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());
|
||||
if (bt == 0xFF)
|
||||
Bitmap->SetIndex(px, py, fg);
|
||||
else {
|
||||
tColor bg = (ColorBg != clrTransparent) ? ColorBg : Bitmap->GetColor(px, py);
|
||||
Bitmap->SetIndex(px, py, Bitmap->Index(Bitmap->Blend(ColorFg, bg, bt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
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 cFont::SetFont(eDvbFont Font, const void *Data)
|
||||
// --- cFont -----------------------------------------------------------------
|
||||
|
||||
cFont *cFont::fonts[eDvbFontSize] = { NULL };
|
||||
|
||||
void cFont::SetFont(eDvbFont Font, const char *Name, int CharHeight)
|
||||
{
|
||||
delete fonts[Font];
|
||||
fonts[Font] = new cFont(Data ? Data : FontData[code][Font]);
|
||||
fonts[Font] = new cFreetypeFont(*Name == '/' ? Name : *AddDirectory(FONTDIR, Name), CharHeight);
|
||||
}
|
||||
|
||||
const cFont *cFont::GetFont(eDvbFont Font)
|
||||
@@ -131,8 +302,13 @@ const cFont *cFont::GetFont(eDvbFont Font)
|
||||
Font = fontOsd;
|
||||
else if (Setup.UseSmallFont == 2)
|
||||
Font = fontSml;
|
||||
if (!fonts[Font])
|
||||
SetFont(Font);
|
||||
if (!fonts[Font]) {
|
||||
switch (Font) {
|
||||
case fontOsd: SetFont(Font, AddDirectory(FONTDIR, Setup.FontOsd), Setup.FontOsdSize); break;
|
||||
case fontSml: SetFont(Font, AddDirectory(FONTDIR, Setup.FontSml), Setup.FontSmlSize); break;
|
||||
case fontFix: SetFont(Font, AddDirectory(FONTDIR, Setup.FontFix), Setup.FontFixSize); break;
|
||||
}
|
||||
}
|
||||
return fonts[Font];
|
||||
}
|
||||
|
||||
@@ -176,16 +352,18 @@ void cTextWrapper::Set(const char *Text, const cFont *Font, int Width)
|
||||
stripspace(text); // strips trailing newlines
|
||||
|
||||
for (char *p = text; *p; ) {
|
||||
if (*p == '\n') {
|
||||
int sl = Utf8CharLen(p);
|
||||
uint sym = Utf8CharGet(p, sl);
|
||||
if (sym == '\n') {
|
||||
lines++;
|
||||
w = 0;
|
||||
Blank = Delim = NULL;
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
else if (isspace(*p))
|
||||
else if (sl == 1 && isspace(sym))
|
||||
Blank = p;
|
||||
int cw = Font->Width(*p);
|
||||
int cw = Font->Width(sym);
|
||||
if (w + cw > Width) {
|
||||
if (Blank) {
|
||||
*Blank = '\n';
|
||||
@@ -214,7 +392,7 @@ void cTextWrapper::Set(const char *Text, const cFont *Font, int Width)
|
||||
Delim = p;
|
||||
Blank = NULL;
|
||||
}
|
||||
p++;
|
||||
p += sl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user