vdr  2.0.2
font.c
Go to the documentation of this file.
1 /*
2  * font.c: Font handling for the DVB On Screen Display
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * BiDi support by Osama Alrawab <alrawab@hotmail.com> @2008 Tripoli-Libya.
8  *
9  * $Id: font.c 2.13.1.1 2013/04/07 14:54:15 kls Exp $
10  */
11 
12 #include "font.h"
13 #include <ctype.h>
14 #include <fontconfig/fontconfig.h>
15 #ifdef BIDI
16 #include <fribidi.h>
17 #endif
18 #include <ft2build.h>
19 #include FT_FREETYPE_H
20 #include "config.h"
21 #include "osd.h"
22 #include "tools.h"
23 
24 const char *DefaultFontOsd = "Sans Serif:Bold";
25 const char *DefaultFontSml = "Sans Serif";
26 const char *DefaultFontFix = "Courier:Bold";
27 
28 // --- cFreetypeFont ---------------------------------------------------------
29 
30 #define KERNING_UNKNOWN (-10000)
31 
32 struct tKerning {
33  uint prevSym;
34  int kerning;
35  tKerning(uint PrevSym, int Kerning = 0) { prevSym = PrevSym; kerning = Kerning; }
36  };
37 
38 class cGlyph : public cListObject {
39 private:
40  uint charCode;
42  int advanceX;
43  int advanceY;
44  int left;
45  int top;
46  int width;
47  int rows;
48  int pitch;
50 public:
51  cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData);
52  virtual ~cGlyph();
53  uint CharCode(void) const { return charCode; }
54  uchar *Bitmap(void) const { return bitmap; }
55  int AdvanceX(void) const { return advanceX; }
56  int AdvanceY(void) const { return advanceY; }
57  int Left(void) const { return left; }
58  int Top(void) const { return top; }
59  int Width(void) const { return width; }
60  int Rows(void) const { return rows; }
61  int Pitch(void) const { return pitch; }
62  int GetKerningCache(uint PrevSym) const;
63  void SetKerningCache(uint PrevSym, int Kerning);
64  };
65 
66 cGlyph::cGlyph(uint CharCode, FT_GlyphSlotRec_ *GlyphData)
67 {
69  advanceX = GlyphData->advance.x >> 6;
70  advanceY = GlyphData->advance.y >> 6;
71  left = GlyphData->bitmap_left;
72  top = GlyphData->bitmap_top;
73  width = GlyphData->bitmap.width;
74  rows = GlyphData->bitmap.rows;
75  pitch = GlyphData->bitmap.pitch;
77  memcpy(bitmap, GlyphData->bitmap.buffer, rows * pitch);
78 }
79 
81 {
82  free(bitmap);
83 }
84 
85 int cGlyph::GetKerningCache(uint PrevSym) const
86 {
87  for (int i = kerningCache.Size(); --i > 0; ) {
88  if (kerningCache[i].prevSym == PrevSym)
89  return kerningCache[i].kerning;
90  }
91  return KERNING_UNKNOWN;
92 }
93 
94 void cGlyph::SetKerningCache(uint PrevSym, int Kerning)
95 {
96  kerningCache.Append(tKerning(PrevSym, Kerning));
97 }
98 
99 class cFreetypeFont : public cFont {
100 private:
102  int size;
103  int height;
104  int bottom;
105  FT_Library library;
106  FT_Face face;
109  int Bottom(void) const { return bottom; }
110  int Kerning(cGlyph *Glyph, uint PrevSym) const;
111  cGlyph* Glyph(uint CharCode, bool AntiAliased = false) const;
112 public:
113  cFreetypeFont(const char *Name, int CharHeight, int CharWidth = 0);
114  virtual ~cFreetypeFont();
115  virtual const char *FontName(void) const { return fontName; }
116  virtual int Size(void) const { return size; }
117  virtual int Width(uint c) const;
118  virtual int Width(const char *s) const;
119  virtual int Height(void) const { return height; }
120  virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const;
121  virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const;
122  };
123 
124 cFreetypeFont::cFreetypeFont(const char *Name, int CharHeight, int CharWidth)
125 {
126  fontName = Name;
127  size = CharHeight;
128  height = 0;
129  bottom = 0;
130  int error = FT_Init_FreeType(&library);
131  if (!error) {
132  error = FT_New_Face(library, Name, 0, &face);
133  if (!error) {
134  if (face->num_fixed_sizes && face->available_sizes) { // fixed font
135  // TODO what exactly does all this mean?
136  height = face->available_sizes->height;
137  for (uint sym ='A'; sym < 'z'; sym++) { // search for descender for fixed font FIXME
138  FT_UInt glyph_index = FT_Get_Char_Index(face, sym);
139  error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
140  if (!error) {
141  error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
142  if (!error) {
143  if (face->glyph->bitmap.rows-face->glyph->bitmap_top > bottom)
144  bottom = face->glyph->bitmap.rows-face->glyph->bitmap_top;
145  }
146  else
147  esyslog("ERROR: FreeType: error %d in FT_Render_Glyph", error);
148  }
149  else
150  esyslog("ERROR: FreeType: error %d in FT_Load_Glyph", error);
151  }
152  }
153  else {
154  error = FT_Set_Char_Size(face, // handle to face object
155  CharWidth * 64, // CharWidth in 1/64th of points
156  CharHeight * 64, // CharHeight in 1/64th of points
157  0, // horizontal device resolution
158  0); // vertical device resolution
159  if (!error) {
160  height = (face->size->metrics.ascender - face->size->metrics.descender + 63) / 64;
161  bottom = abs((face->size->metrics.descender - 63) / 64);
162  }
163  else
164  esyslog("ERROR: FreeType: error %d during FT_Set_Char_Size (font = %s)\n", error, Name);
165  }
166  }
167  else
168  esyslog("ERROR: FreeType: load error %d (font = %s)", error, Name);
169  }
170  else
171  esyslog("ERROR: FreeType: initialization error %d (font = %s)", error, Name);
172 }
173 
175 {
176  FT_Done_Face(face);
177  FT_Done_FreeType(library);
178 }
179 
180 int cFreetypeFont::Kerning(cGlyph *Glyph, uint PrevSym) const
181 {
182  int kerning = 0;
183  if (Glyph && PrevSym) {
184  kerning = Glyph->GetKerningCache(PrevSym);
185  if (kerning == KERNING_UNKNOWN) {
186  FT_Vector delta;
187  FT_UInt glyph_index = FT_Get_Char_Index(face, Glyph->CharCode());
188  FT_UInt glyph_index_prev = FT_Get_Char_Index(face, PrevSym);
189  FT_Get_Kerning(face, glyph_index_prev, glyph_index, FT_KERNING_DEFAULT, &delta);
190  kerning = delta.x / 64;
191  Glyph->SetKerningCache(PrevSym, kerning);
192  }
193  }
194  return kerning;
195 }
196 
197 cGlyph* cFreetypeFont::Glyph(uint CharCode, bool AntiAliased) const
198 {
199  // Non-breaking space:
200  if (CharCode == 0xA0)
201  CharCode = 0x20;
202 
203  // Lookup in cache:
204  cList<cGlyph> *glyphCache = AntiAliased ? &glyphCacheAntiAliased : &glyphCacheMonochrome;
205  for (cGlyph *g = glyphCache->First(); g; g = glyphCache->Next(g)) {
206  if (g->CharCode() == CharCode)
207  return g;
208  }
209 
210  FT_UInt glyph_index = FT_Get_Char_Index(face, CharCode);
211 
212  // Load glyph image into the slot (erase previous one):
213  int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
214  if (error)
215  esyslog("ERROR: FreeType: error during FT_Load_Glyph");
216  else {
217 #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?
218  if (AntiAliased || CharCode == 32)
219 #else
220  if (AntiAliased)
221 #endif
222  error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
223  else
224  error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO);
225  if (error)
226  esyslog("ERROR: FreeType: error during FT_Render_Glyph %d, %d\n", CharCode, glyph_index);
227  else { //new bitmap
228  cGlyph *Glyph = new cGlyph(CharCode, face->glyph);
229  glyphCache->Add(Glyph);
230  return Glyph;
231  }
232  }
233 #define UNKNOWN_GLYPH_INDICATOR '?'
234  if (CharCode != UNKNOWN_GLYPH_INDICATOR)
235  return Glyph(UNKNOWN_GLYPH_INDICATOR, AntiAliased);
236  return NULL;
237 }
238 
239 int cFreetypeFont::Width(uint c) const
240 {
241  cGlyph *g = Glyph(c, Setup.AntiAlias);
242  return g ? g->AdvanceX() : 0;
243 }
244 
245 int cFreetypeFont::Width(const char *s) const
246 {
247  int w = 0;
248  if (s) {
249 #ifdef BIDI
250  cString bs = Bidi(s);
251  s = bs;
252 #endif
253  uint prevSym = 0;
254  while (*s) {
255  int sl = Utf8CharLen(s);
256  uint sym = Utf8CharGet(s, sl);
257  s += sl;
258  cGlyph *g = Glyph(sym, Setup.AntiAlias);
259  if (g)
260  w += g->AdvanceX() + Kerning(g, prevSym);
261  prevSym = sym;
262  }
263  }
264  return w;
265 }
266 
267 #define MAX_BLEND_LEVELS 256
268 
269 void cFreetypeFont::DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const
270 {
271  if (s && height) { // checking height to make sure we actually have a valid font
272 #ifdef BIDI
273  cString bs = Bidi(s);
274  s = bs;
275 #endif
276  bool AntiAliased = Setup.AntiAlias && Bitmap->Bpp() >= 8;
277  bool TransparentBackground = ColorBg == clrTransparent;
278  int16_t BlendLevelIndex[MAX_BLEND_LEVELS]; // tIndex is 8 bit unsigned, so a negative value can be used to mark unused entries
279  if (AntiAliased && !TransparentBackground)
280  memset(BlendLevelIndex, 0xFF, sizeof(BlendLevelIndex)); // initializes the array with negative values
281  tIndex fg = Bitmap->Index(ColorFg);
282  uint prevSym = 0;
283  while (*s) {
284  int sl = Utf8CharLen(s);
285  uint sym = Utf8CharGet(s, sl);
286  s += sl;
287  cGlyph *g = Glyph(sym, AntiAliased);
288  if (!g)
289  continue;
290  int kerning = Kerning(g, prevSym);
291  prevSym = sym;
292  uchar *buffer = g->Bitmap();
293  int symWidth = g->Width();
294  if (Width && x + symWidth + g->Left() + kerning - 1 > Width)
295  break; // we don't draw partial characters
296  if (x + symWidth + g->Left() + kerning > 0) {
297  for (int row = 0; row < g->Rows(); row++) {
298  for (int pitch = 0; pitch < g->Pitch(); pitch++) {
299  uchar bt = *(buffer + (row * g->Pitch() + pitch));
300  if (AntiAliased) {
301  if (bt > 0x00) {
302  int px = x + pitch + g->Left() + kerning;
303  int py = y + row + (height - Bottom() - g->Top());
304  tColor bg;
305  if (bt == 0xFF)
306  bg = fg;
307  else if (TransparentBackground)
308  bg = Bitmap->Index(Bitmap->Blend(ColorFg, Bitmap->GetColor(px, py), bt));
309  else if (BlendLevelIndex[bt] >= 0)
310  bg = BlendLevelIndex[bt];
311  else
312  bg = BlendLevelIndex[bt] = Bitmap->Index(Bitmap->Blend(ColorFg, ColorBg, bt));
313  Bitmap->SetIndex(px, py, bg);
314  }
315  }
316  else { //monochrome rendering
317  for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) {
318  if (bt & 0x80)
319  Bitmap->SetIndex(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top()), fg);
320  bt <<= 1;
321  }
322  }
323  }
324  }
325  }
326  x += g->AdvanceX() + kerning;
327  if (x > Bitmap->Width() - 1)
328  break;
329  }
330  }
331 }
332 
333 void cFreetypeFont::DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const
334 {
335  if (s && height) { // checking height to make sure we actually have a valid font
336 #ifdef BIDI
337  cString bs = Bidi(s);
338  s = bs;
339 #endif
340  bool AntiAliased = Setup.AntiAlias;
341  uint prevSym = 0;
342  while (*s) {
343  int sl = Utf8CharLen(s);
344  uint sym = Utf8CharGet(s, sl);
345  s += sl;
346  cGlyph *g = Glyph(sym, AntiAliased);
347  if (!g)
348  continue;
349  int kerning = Kerning(g, prevSym);
350  prevSym = sym;
351  uchar *buffer = g->Bitmap();
352  int symWidth = g->Width();
353  if (Width && x + symWidth + g->Left() + kerning - 1 > Width)
354  break; // we don't draw partial characters
355  if (x + symWidth + g->Left() + kerning > 0) {
356  for (int row = 0; row < g->Rows(); row++) {
357  for (int pitch = 0; pitch < g->Pitch(); pitch++) {
358  uchar bt = *(buffer + (row * g->Pitch() + pitch));
359  if (AntiAliased) {
360  if (bt > 0x00)
361  Pixmap->DrawPixel(cPoint(x + pitch + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), AlphaBlend(ColorFg, ColorBg, bt));
362  }
363  else { //monochrome rendering
364  for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) {
365  if (bt & 0x80)
366  Pixmap->DrawPixel(cPoint(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), ColorFg);
367  bt <<= 1;
368  }
369  }
370  }
371  }
372  }
373  x += g->AdvanceX() + kerning;
374  if (x > Pixmap->DrawPort().Width() - 1)
375  break;
376  }
377  }
378 }
379 
380 // --- cDummyFont ------------------------------------------------------------
381 
382 // A dummy font, in case there are no fonts installed:
383 
384 class cDummyFont : public cFont {
385 public:
386  virtual int Width(uint c) const { return 10; }
387  virtual int Width(const char *s) const { return 50; }
388  virtual int Height(void) const { return 20; }
389  virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {}
390  virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {};
391  };
392 
393 // --- cFont -----------------------------------------------------------------
394 
395 cFont *cFont::fonts[eDvbFontSize] = { NULL };
396 
397 void cFont::SetFont(eDvbFont Font, const char *Name, int CharHeight)
398 {
399  cFont *f = CreateFont(Name, constrain(CharHeight, MINFONTSIZE, MAXFONTSIZE));
400  if (!f || !f->Height())
401  f = new cDummyFont;
402  delete fonts[Font];
403  fonts[Font] = f;
404 }
405 
407 {
408  if (Setup.UseSmallFont == 0 && Font == fontSml)
409  Font = fontOsd;
410  else if (Setup.UseSmallFont == 2)
411  Font = fontSml;
412  if (!fonts[Font]) {
413  switch (Font) {
414  case fontOsd: SetFont(Font, Setup.FontOsd, Setup.FontOsdSize); break;
415  case fontSml: SetFont(Font, Setup.FontSml, min(Setup.FontSmlSize, Setup.FontOsdSize)); break;
416  case fontFix: SetFont(Font, Setup.FontFix, Setup.FontFixSize); break;
417  default: esyslog("ERROR: unknown Font %d (%s %d)", Font, __FUNCTION__, __LINE__);
418  }
419  }
420  return fonts[Font];
421 }
422 
423 cFont *cFont::CreateFont(const char *Name, int CharHeight, int CharWidth)
424 {
425  cString fn = GetFontFileName(Name);
426  if (*fn)
427  return new cFreetypeFont(fn, CharHeight, CharWidth);
428  return NULL;
429 }
430 
431 bool cFont::GetAvailableFontNames(cStringList *FontNames, bool Monospaced)
432 {
433  if (!FontNames->Size()) {
434  FcInit();
435  FcObjectSet *os = FcObjectSetBuild(FC_FAMILY, FC_STYLE, NULL);
436  FcPattern *pat = FcPatternCreate();
437  FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
438  if (Monospaced)
439  FcPatternAddInteger(pat, FC_SPACING, FC_MONO);
440  FcFontSet* fontset = FcFontList(NULL, pat, os);
441  for (int i = 0; i < fontset->nfont; i++) {
442  char *s = (char *)FcNameUnparse(fontset->fonts[i]);
443  if (s) {
444  // Strip i18n stuff:
445  char *c = strchr(s, ':');
446  if (c) {
447  char *p = strchr(c + 1, ',');
448  if (p)
449  *p = 0;
450  }
451  char *p = strchr(s, ',');
452  if (p) {
453  if (c)
454  memmove(p, c, strlen(c) + 1);
455  else
456  *p = 0;
457  }
458  // Make it user presentable:
459  s = strreplace(s, "\\", ""); // '-' is escaped
460  s = strreplace(s, "style=", "");
461  FontNames->Append(s); // takes ownership of s
462  }
463  }
464  FcFontSetDestroy(fontset);
465  FcPatternDestroy(pat);
466  FcObjectSetDestroy(os);
467  //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once
468  FontNames->Sort();
469  }
470  return FontNames->Size() > 0;
471 }
472 
473 cString cFont::GetFontFileName(const char *FontName)
474 {
475  cString FontFileName;
476  if (FontName) {
477  char *fn = strdup(FontName);
478  fn = strreplace(fn, ":", ":style=");
479  fn = strreplace(fn, "-", "\\-");
480  FcInit();
481  FcPattern *pat = FcNameParse((FcChar8 *)fn);
482  FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
483  FcConfigSubstitute(NULL, pat, FcMatchPattern);
484  FcDefaultSubstitute(pat);
485  FcResult fresult;
486  FcFontSet *fontset = FcFontSort(NULL, pat, FcFalse, NULL, &fresult);
487  if (fontset) {
488  for (int i = 0; i < fontset->nfont; i++) {
489  FcBool scalable;
490  FcPatternGetBool(fontset->fonts[i], FC_SCALABLE, 0, &scalable);
491  if (scalable) {
492  FcChar8 *s = NULL;
493  FcPatternGetString(fontset->fonts[i], FC_FILE, 0, &s);
494  FontFileName = (char *)s;
495  break;
496  }
497  }
498  FcFontSetDestroy(fontset);
499  }
500  else
501  esyslog("ERROR: no usable font found for '%s'", FontName);
502  FcPatternDestroy(pat);
503  free(fn);
504  //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than once
505  }
506  return FontFileName;
507 }
508 
509 #ifdef BIDI
510 cString cFont::Bidi(const char *Ltr)
511 {
512  if (!cCharSetConv::SystemCharacterTable()) { // bidi requires UTF-8
513  fribidi_set_mirroring(true);
514  fribidi_set_reorder_nsm(false);
515  FriBidiCharSet fribidiCharset = FRIBIDI_CHAR_SET_UTF8;
516  int LtrLen = strlen(Ltr);
517  FriBidiCharType Base = FRIBIDI_TYPE_L;
518  FriBidiChar *Logical = MALLOC(FriBidiChar, LtrLen + 1) ;
519  int RtlLen = fribidi_charset_to_unicode(fribidiCharset, const_cast<char *>(Ltr), LtrLen, Logical);
520  FriBidiChar *Visual = MALLOC(FriBidiChar, LtrLen + 1) ;
521  char *Rtl = NULL;
522  bool ok = fribidi_log2vis(Logical, RtlLen, &Base, Visual, NULL, NULL, NULL);
523  if (ok) {
524  fribidi_remove_bidi_marks(Visual, RtlLen, NULL, NULL, NULL);
525  Rtl = MALLOC(char, RtlLen * 4 + 1);
526  fribidi_unicode_to_charset(fribidiCharset, Visual, RtlLen, Rtl);
527  }
528  free(Logical);
529  free(Visual);
530  if (ok)
531  return cString(Rtl, true);
532  }
533  return cString(Ltr);
534 }
535 #endif
536 
537 // --- cTextWrapper ----------------------------------------------------------
538 
540 {
541  text = eol = NULL;
542  lines = 0;
543  lastLine = -1;
544 }
545 
546 cTextWrapper::cTextWrapper(const char *Text, const cFont *Font, int Width)
547 {
548  text = NULL;
549  Set(Text, Font, Width);
550 }
551 
553 {
554  free(text);
555 }
556 
557 void cTextWrapper::Set(const char *Text, const cFont *Font, int Width)
558 {
559  free(text);
560  text = Text ? strdup(Text) : NULL;
561  eol = NULL;
562  lines = 0;
563  lastLine = -1;
564  if (!text)
565  return;
566  lines = 1;
567  if (Width <= 0)
568  return;
569 
570  char *Blank = NULL;
571  char *Delim = NULL;
572  int w = 0;
573 
574  stripspace(text); // strips trailing newlines
575 
576  for (char *p = text; *p; ) {
577  int sl = Utf8CharLen(p);
578  uint sym = Utf8CharGet(p, sl);
579  if (sym == '\n') {
580  lines++;
581  w = 0;
582  Blank = Delim = NULL;
583  p++;
584  continue;
585  }
586  else if (sl == 1 && isspace(sym))
587  Blank = p;
588  int cw = Font->Width(sym);
589  if (w + cw > Width) {
590  if (Blank) {
591  *Blank = '\n';
592  p = Blank;
593  continue;
594  }
595  else if (w > 0) { // there has to be at least one character before the newline
596  // Here's the ugly part, where we don't have any whitespace to
597  // punch in a newline, so we need to make room for it:
598  if (Delim)
599  p = Delim + 1; // let's fall back to the most recent delimiter
600  char *s = MALLOC(char, strlen(text) + 2); // The additional '\n' plus the terminating '\0'
601  int l = p - text;
602  strncpy(s, text, l);
603  s[l] = '\n';
604  strcpy(s + l + 1, p);
605  free(text);
606  text = s;
607  p = text + l;
608  continue;
609  }
610  }
611  w += cw;
612  if (strchr("-.,:;!?_", *p)) {
613  Delim = p;
614  Blank = NULL;
615  }
616  p += sl;
617  }
618 }
619 
620 const char *cTextWrapper::Text(void)
621 {
622  if (eol) {
623  *eol = '\n';
624  eol = NULL;
625  }
626  return text;
627 }
628 
629 const char *cTextWrapper::GetLine(int Line)
630 {
631  char *s = NULL;
632  if (Line < lines) {
633  if (eol) {
634  *eol = '\n';
635  if (Line == lastLine + 1)
636  s = eol + 1;
637  eol = NULL;
638  }
639  if (!s) {
640  s = text;
641  for (int i = 0; i < Line; i++) {
642  s = strchr(s, '\n');
643  if (s)
644  s++;
645  else
646  break;
647  }
648  }
649  if (s) {
650  if ((eol = strchr(s, '\n')) != NULL)
651  *eol = 0;
652  }
653  lastLine = Line;
654  }
655  return s;
656 }
657