mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Changed -O2 to -O3 in Make.config.template (reported by Matti Lehtimäki). - Added a missing 'default' case in cPixmapMemory::DrawEllipse(). - Fixed some direct comparisons of double values. - Fixed detecting frames on channels that broadcast with separate "fields" instead of complete frames. - Made updating the editing marks during replay react faster in case the marks file has just been written (with a patch from Udo Richter). - Fixed horizontal scaling of subtitles (reported by Reinhard Nissl). - Stripped the note "The data returned by this function is only used for informational purposes (if any)" from the description of cDevice::GetVideoSize(). The VideoAspect is now used to properly scale subtitles. - Fixed cUnbufferedFile::Seek() in case it is compiled without USE_FADVISE (thanks to Juergen Lock). - Fixed the Language header of the Serbian translation file (thanks to Ville Skyttä). - Added anti-aliasing when upscaling bitmaps, which improves the display of SD subtitles when replayed on an HD OSD (thanks to Reinhard Nissl for his help in debugging). - Renamed cBitmap::Scale() to Scaled(), because it doesn't modify the bitmap itself, but rather returns a scaled copy. - Fixed the description of cReceiver in PLUGINS.html, regarding detaching a receiver from its device before deleting it (reported by Winfried Köhler). This change in behavior was introduced in version 1.5.7. - Fixed scaling subtitles in case the OSD size is exactly the same as the display size of the subtitles. - Added a missing initialization to sDvbSpuRect (reported by Sergiu Dotenco). - Replaced "%lld" and "%llX" print format specifiers with "PRId64" and "PRIX64" to avoid compiler warnings with gcc 4.5.2 (thanks to Sergiu Dotenco). On a personal note: I find it a step in the totally wrong direction that there have been macros introduced to work around this problem in the first place. There should have been "real" format specifiers defined that address this. These macros are nothing but an ugly workaround. - Added Cancel(3) to ~cTrueColorDemo() in the "osddemo" plugin (thanks to Reinhard Nissl). - Added a missing font deletion in cTrueColorDemo::Action() in the "osddemo" plugin (thanks to Reinhard Nissl). - Fixed a buffer overflow in cFont::Bidi() (thanks to Reinhard Nissl). - Added HD stream content identifiers to vdr.5 (thanks to Christoph Haubrich). - Made cRecordingInfo::Read(FILE *f) private to avoid calls to it from outside cRecordingInfo or cRecording (reported by Mika Laitio). - The dvbhddevice plugin is now part of the VDR distribution archive (thanks to Andreas Regel). - Removed an obsolete local variable in dvbsdffosd.c (thanks to Paul Menzel). - Fixed a possible NULL pointer dereference in osddemo.c (reported by Paul Menzel). - Now using pkg-config to get fribidi, freetype and fontconfig cflags and libs (thanks to Ville Skyttä). - The Makefile now also installs the include files (thanks to Ville Skyttä). - Added handling of "ANSI/SCTE 57" descriptors (thanks too Rolf Ahrenberg). - Avoiding an unecessary call to Recordings.ResetResume() (thanks to Reinhard Nissl).
655 lines
21 KiB
C
655 lines
21 KiB
C
/*
|
|
* 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 <alrawab@hotmail.com> @2008 Tripoli-Libya.
|
|
*
|
|
* $Id: font.c 2.8 2011/03/28 16:29:51 kls Exp $
|
|
*/
|
|
|
|
#include "font.h"
|
|
#include <ctype.h>
|
|
#include <fontconfig/fontconfig.h>
|
|
#ifdef BIDI
|
|
#include <fribidi.h>
|
|
#endif
|
|
#include <ft2build.h>
|
|
#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<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);
|
|
};
|
|
|
|
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);
|
|
memcpy(bitmap, GlyphData->bitmap.buffer, rows * pitch);
|
|
}
|
|
|
|
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 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, int CharWidth = 0);
|
|
virtual ~cFreetypeFont();
|
|
virtual const char *FontName(void) const { return fontName; }
|
|
virtual int Size(void) const { return size; }
|
|
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;
|
|
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
|
|
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<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;
|
|
}
|
|
}
|
|
#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 {
|
|
public:
|
|
virtual int Width(uint c) const { return 10; }
|
|
virtual int Width(const char *s) const { return 50; }
|
|
virtual int Height(void) const { return 20; }
|
|
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)
|
|
{
|
|
cFont *f = CreateFont(Name, min(max(CharHeight, MINFONTSIZE), MAXFONTSIZE));
|
|
if (!f || !f->Height())
|
|
f = new cDummyFont;
|
|
delete fonts[Font];
|
|
fonts[Font] = f;
|
|
}
|
|
|
|
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, Setup.FontSmlSize); 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);
|
|
if (*fn)
|
|
return new cFreetypeFont(fn, CharHeight, CharWidth);
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
FcFontSet *fontset = FcFontSort(NULL, pat, FcFalse, NULL, NULL);
|
|
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)
|
|
{
|
|
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<char *>(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 {
|
|
// 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;
|
|
}
|
|
}
|
|
else
|
|
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;
|
|
}
|