mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
Original announce message: VDR developer version 1.7.28 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.28.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.27-1.7.28.diff MD5 checksums: 3ccff2dcc42d112e23dd64f2c39f02f1 vdr-1.7.28.tar.bz2 7249ead4aca4b24e53d49d11c67e1613 vdr-1.7.27-1.7.28.diff WARNING: ======== This is a developer version. Even though I use it in my productive environment. I strongly recommend that you only use it under controlled conditions and for testing and debugging. The new default skin "LCARS" displays the signal strengths and qualities of all devices in its main menu. For devices that have an stb0899 frontend chip (like the TT-budget S2-3200) retrieving this information from the driver is rather slow, which results in a sluggish response to user input in the main menu. To speed this up you may want to apply the patches from From the HISTORY file: - Fixed cPixmapMemory::DrawEllipse() for quadrants -1 and -4. - Fixed getting the maximum short channel name length in case there are no short names at all (reported by Derek Kelly). - The new function cDevice::DeviceType() returns a string identifying the type of the given device. - Now limiting the number of characters of a channel's (short) name to 16 in the schedules menus, to keep that column from getting overly wide in case there is a channel with a very long name that has no short name. - Fixed EPG scan on systems with only a single DVB device that use software output (reported by Juergen Lock). - Skins can now inquire the menu category for which their cSkinDisplayMenu is currently being used. This can be done either through a call to cSkinDisplayMenu::MenuCategory() or by reimplementing cSkinDisplayMenu::SetMenuCategory(). This information allows a skin to use special icons or decorations for the various types of menus in VDR. - The new setup option "DVB/Standard compliance" can be used to switch between different variations of the DVB standard (thanks to Rolf Ahrenberg). Currently there is "DVB" (for the original DVB standard) and "ANSI/SCTE", which is used to properly handle certain private stream types. - The disk usage is no longer automatically added to the title of the main and "Recordings" menus. This has always been a mekeshift solution and it is now up to the individual skin if, where and how it wants to display this information. A skin can use the new cVideoDiskUsage class to implement such a display. For compatibility, the default skins "Classic VDR", "ST:TNG Panels" and "Text mode" (i.e. curses) have been changed to behave like before. Other skins may want to display the disk usage in totally different ways. - A cOsdMenu can now handle skins that display different numbers of items in the various menu categories. - OSD and skin are now reinitialized after a plugin setup page has been confirmed, to have them react immediately in case any change to a plugin's setup parameter has an effect on the OSD. - The Timers list is now marked as modified whenever a recording starts or ends. - Fixed cDevice::StillPicture(), making sure it doesn't call the derived class's function if no buffer has been allocated (reported by Marcus Roscher). - Fixed the SVDRP command UPDR, which didn't update the global recordings list (reported by Lars Hanisch). - cControl::Control() now has an additional boolean parameter, which can be set to true to get the current player control even if it is hidden. - The new functions cControl::GetRecording() and cControl::GetHeader() can be used to retrieve information about what the current player is playing. - Fixed a possible high CPU load when pausing replay (thanks to Reinhard Nissl). - Fixed character comparisons in cSubtitleObject::DecodeCharacterString() (reported by Reinhard Mantey). - Renamed the function cString::sprintf(const char *fmt, va_list &ap) to vsprintf(), because it might inadvertently be called with a 'char *' as the second argument on some compilers and cause a crash (reported by Sundararaj Reel). - Removed the "bondedMasterFailed" mechanism from cDvbTuner, because it caused problems with the EPG scan in case a transponder is not receivable in a setup with bonded devices (reported by Michael Schneider). - Making sure setup strings don't contain any newline characters (thanks to Joachim Wilke). - The new member function cSkinDisplayReplay::SetRecording() allows a skin to display more information about the currently played recording. - Fixed a mismatched 'delete' in cSchedules::SetEpgDataFileName() (thanks to Reinhard Mantey). - The DrawText() functions of the OSD now accept the new alignment flag taBorder, which triggers keeping a proper distance from the edge that taLeft or taRight aligns to. - Fixed checking for UTF-8 support in cFont::Bidi() (reported by Torsten Lang). - If a recording has no info file, the 'title' of the recording's info is now set to the recording's name. - cVector::Clear() now reinitializes any previously used members. - Fixed resetting CAMs (thanks to Marco Skambraks). - The new function RgbShade() (include osd.h) can be used to generate a brighter or darker version of a given color. - The new class cSortedTimers can be used to quickly get a list of all timers, sorted by their start time. - The new skin "LCARS" is an enhanced version of the "ST:TNG" skin (which is still there in its original layout, for those who don't like the LCARS skin, or can't use it due to OSD limitations). The LCARS skin utilizes the new "menu category" feature to display additional information on the main menu page. It shows upcoming timers and the system's devices, as well as which device is recording which timers. The upper pane of the main menu displays the programme data in live and replay mode, and a progress bar. An indicator on the right side of the device list shows which device is currently used for live viewing, and whether it is in transfer mode. The individual device displays show the device number, the device type, which CAM (if any ) is currently assigned to the device, and the signal strength and quality. On the left side of the OSD there is a permanent display of the current date and time, the disk usage and the system load. "LCARS" is the new default skin of VDR. It requires at least a 4bpp (16 color) full screen OSD, but you can still operate it if your OSD can handle only fewer colors (in which case you may want to switch to the "ST:TNG" or "Classic VDR" skin). - Finally removed the code marked with __RECORDING_H_DEPRECATED_DIRECT_MEMBER_ACCESS and LEGACY_CRECEIVER. - Now making sure that the "small font" is never larger than the "osd font". - Fixed font handling with fontconfig 2.9.0 or newer (thanks to Joerg Bornkessel). - Extended the interface to the script that gets called for recordings, so that in the "edited" case it also provides the name of the original recording (thanks to Christian Richter). - Added DeleteEvent() to the EPG handler interface, so that an EPG handler can trigger deleting of an event (thanks to Christian Kaiser). - Speeded up opening menus on systems with many (several thousands) of recordings, by caching the information whether a recording is stored on the video directory file system within the cRecording data (based on a patch from Torsten Lang).
658 lines
21 KiB
C
658 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.13 2012/06/02 13:38:28 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, constrain(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, 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);
|
|
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);
|
|
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<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;
|
|
}
|