#include "Draw.h" NAMESPACE_UPP #ifdef PLATFORM_XFT #define LLOG(x) //LOG(x) #define LTIMING(x) //TIMING(x) struct XFTFontFaceInfo { String name; bool fixed:1; bool scaleable:1; bool compose:1; XFTFontFaceInfo() { fixed = scaleable = false; } }; ArrayMap& XFTFontFace() { static ArrayMap x; return x; } FontInfo::Data::Data() { count = 1; for(int i = 0; i < 256; i++) width[i] = NULL; xftfont = NULL; default_width = NULL; } FontInfo::Data::~Data() { if(xftfont) XftFontClose(Xdisplay, xftfont); for(int i = 0; i < 256; i++) if(width[i] && width[i] != default_width) delete[] width[i]; if(default_width) delete[] default_width; } void FontInfo::Data::GetMetrics(int page, CharMetrics *t) { LTIMING("GetMetrics"); LLOG("GetMetrics " << font << " char:" << page * 256 << ", 256"); if(xftfont) { wchar h[1];//TODO Optimize!!! for(int i = 0; i < 256; i++) { LTIMING("XftTextExtents16"); *h = (page << 8) + i; XGlyphInfo info; XftTextExtents16(Xdisplay, xftfont, h, 1, &info); t[i].width = info.xOff; t[i].lspc = -info.x; t[i].rspc = info.xOff - info.width + info.x; } return; } } char *basic_fonts[] = { "sans-serif", "serif", "sans-serif", "monospace", "serif", "sans-serif", "monospace", }; static bool sCheckComposed(const char *face) { XftFont *xftfont = XftFontOpen(Xdisplay, Xscreenno, XFT_FAMILY, XftTypeString, (char *)face, XFT_PIXEL_SIZE, XftTypeInteger, 20, (void *)0); int n = 0; for(int c = 0; c < 128; c++) if(!XftCharExists(Xdisplay, xftfont, c + 256)) n++; XftFontClose(Xdisplay, xftfont); return n > 10; } void Draw::InitPlatformFonts() { for(int i = 0; i < __countof(basic_fonts); i++) { XFTFontFaceInfo& f = XFTFontFace().Add(basic_fonts[i]); f.name = basic_fonts[i]; f.scaleable = true; f.fixed = i == 3 || i == 6; f.compose = sCheckComposed(basic_fonts[i]); } FcFontSet *fs = XftListFonts(Xdisplay, Xscreenno, (void *)0, XFT_FAMILY, XFT_SPACING, XFT_SCALABLE, (void *)0); for(int i = 0; i < fs->nfont; i++) { FcChar8 *family = NULL; if(FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == 0 && family) { XFTFontFaceInfo& f = XFTFontFace().GetAdd((char *)family); int spacing; if(FcPatternGetInteger(fs->fonts[i], FC_SPACING, 0, &spacing) == 0 && spacing == XFT_MONO) f.fixed = true; FcBool scaleable; if(FcPatternGetBool(fs->fonts[i], FC_SCALABLE, 0, &scaleable) == 0 && scaleable) f.scaleable = true; f.compose = sCheckComposed((char *)family); } } /* static const char *replacements[] = { "Bitstream Vera Sans", "Times New Roman;Bitstream Vera Serif", "Bitstream Vera Sans;Arial", "Courier New;Bitstream Vera Sans Mono", "Times New Roman;Bitstream Vera Serif", "Arial;Bitstream Vera Sans", "Courier New;Bitstream Vera Sans Mono", }; for(int i = 0; i < 7; i++) { Vector f = Split(replacements[i], ';'); for(int q = 0; q < f.GetCount(); q++) if(XFTFontFace().Find(f[q]) >= 0) { basic_fonts[i] = PermanentCopy(f[q]); XFTFontFace()[i].compose = sCheckComposed(basic_fonts[i]); break; } }*/ FcFontSetDestroy(fs); } int Font::GetFaceCount() { if(!Draw::sFini) Draw::InitFonts(); return XFTFontFace().GetCount(); } String Font::GetFaceName(int index) { if(!Draw::sFini) Draw::InitFonts(); return index >= 0 && index < XFTFontFace().GetCount() ? XFTFontFace().GetKey(index) : Null; } dword Font::GetFaceInfo(int index) { if(!Draw::sFini) Draw::InitFonts(); dword w = 0; if(index >= 0 && index < XFTFontFace().GetCount()) { XFTFontFaceInfo& fi = XFTFontFace()[index]; if(fi.fixed) w |= FIXEDPITCH; if(fi.scaleable) w |= SCALEABLE; if(fi.compose) w |= COMPOSED; } return w; } XftFont *Draw::CreateXftFont(Font font, int angle) { LTIMING("CreateXftFont"); XftFont *xftfont; double sina, cosa; int hg = abs(font.GetHeight()); if(hg == 0) hg = 10; int i = font.GetFace(); if(i < 0 || i >= XFTFontFace().GetCount()) i = 0; const char *face = i < 7 ? basic_fonts[i] : ~XFTFontFace().GetKey(i); if(angle) { XftMatrix mx; SinCos(angle, sina, cosa); mx.xx = cosa; mx.xy = -sina; mx.yx = sina; mx.yy = cosa; xftfont = XftFontOpen(Xdisplay, Xscreenno, XFT_FAMILY, XftTypeString, face, XFT_SLANT, XftTypeInteger, int(font.IsItalic() ? 110 : 0), XFT_PIXEL_SIZE, XftTypeInteger, hg, XFT_MATRIX, XftTypeMatrix, &mx, XFT_WEIGHT, XftTypeInteger, int(font.IsBold() ? 200 : 100), XFT_ANTIALIAS, XftTypeBool, FcBool(!font.IsNonAntiAliased()), XFT_MINSPACE, XftTypeBool, (FcBool)1, (void *)0); } else xftfont = XftFontOpen(Xdisplay, Xscreenno, XFT_FAMILY, XftTypeString, face, XFT_SLANT, XftTypeInteger, int(font.IsItalic() ? 110 : 0), XFT_PIXEL_SIZE, XftTypeInteger, hg, XFT_WEIGHT, XftTypeInteger, int(font.IsBold() ? 200 : 100), XFT_ANTIALIAS, XftTypeBool, FcBool(!font.IsNonAntiAliased()), XFT_MINSPACE, XftTypeBool, (FcBool)1, (void *)0); return xftfont; } FontInfo Draw::Acquire(Font font, int angle, int device) { LTIMING("Acquire"); if(IsNull(font)) font = StdFont(); if(font.GetFace() == 0) font.Face(AStdFont.GetFace()); if(font.GetHeight() == 0) font.Height(AStdFont.GetHeight()); FontInfo::Data *f, *fh; f = fh = GetFontHash((font.GetHashValue() ^ angle ^ (device << 9)) % (int)FONTHASH); LLOG("Acquire " << font << " device: " << device); for(;;) { f = f->GetNext(HASH); if(f == fh) break; if(f->font == font && f->angle == angle && f->device == device) { LLOG("Reusing " << f->font << " count:" << f->count); if(f->InList(LRU)) { f->Unlink(LRU); FontCached--; LLOG("Removing from cache " << f->font << " count:" << f->count << " cached:" << FontCached); } f->count++; return f; } } LLOG("New " << font); LTIMING("Acquire New"); f = fh->InsertNext(HASH); f->font = font; f->angle = angle; f->device = device; int hg = abs(font.GetHeight()); if(hg == 0) hg = 10; f->xftfont = CreateXftFont(font, angle); f->filename = NULL; f->ascent = (int16)f->xftfont->ascent; f->descent = (int16)f->xftfont->descent; f->height = f->ascent + f->descent; f->lineheight = (int16)f->xftfont->height; f->external = 0; f->internal = 0; f->overhang = 0; f->maxwidth = (int16)f->xftfont->max_advance_width; f->avewidth = f->maxwidth; f->default_char = '?'; f->fixedpitch = font.GetFaceInfo() & Font::FIXEDPITCH; unsigned long a; f->underline_thickness = max(hg / 20, 1); f->underline_position = max(hg / 15, int(f->descent > 0)); if(angle) { SinCos(angle, f->sina, f->cosa); f->offset.cx = fround(f->ascent * f->sina); f->offset.cy = fround(f->ascent * f->cosa); } FontInfo fi = FontInfo(f); fi.GetPage(0); return fi; } String FontInfo::GetFileName() const { if(IsNull(ptr->filename)) { char *fn = NULL; XftPatternGetString(ptr->xftfont->pattern, XFT_FILE, 0, &fn); if(fn) ptr->filename = fn; } return ptr->filename; } void Draw::DrawTextOp(int x, int y, int angle, const wchar *text, Font font, Color ink, int n, const int *dx) { LTIMING("DrawText"); LLOG("DrawText " << ToUtf8(WString(text, n)) << " color:" << ink << " font:" << font); //TODO - X11 seems to crash when displaying too long strings (?) int ox = x + actual_offset.x; int oy = y + actual_offset.y; SetForeground(ink); SetFont(font, angle); const FontInfo::Data *fd = lastFont.ptr; XftColor c; c.color.red = ink.GetR() << 8; c.color.green = ink.GetG() << 8; c.color.blue = ink.GetB() << 8; c.color.alpha = 0xffff; c.pixel = GetXPixel(ink.GetR(), ink.GetG(), ink.GetB()); if(angle) { int xpos = 0; int xpp = 0; int pfi = -1; for(int i = 0; i < n; i++) { wchar h = text[i]; XftDrawString16(xftdraw, &c, fd->xftfont, int(ox + xpos * fd->cosa + fd->offset.cx), int(oy - xpos * fd->sina + fd->offset.cy), (FcChar16 *)&h, 1); xpos += dx ? dx[i] : lastFont[text[i]]; } if(font.IsUnderline() || font.IsStrikeout()) { xpos = xpp + lastFont[(byte)text[n - 1]]; if(font.IsUnderline()) DrawLine( int(x + fd->offset.cx + fd->underline_position * fd->sina), int(y + fd->offset.cy + fd->underline_position * fd->cosa), int(x + xpos * fd->cosa + fd->offset.cx + fd->underline_position * fd->sina), int(y - xpos * fd->sina + fd->offset.cy + fd->underline_position * fd->cosa), fd->underline_thickness, ink ); if(font.IsStrikeout()) { int p = 2 * fd->ascent / 3; DrawLine( int(x + p * fd->sina), int(y + p * fd->cosa), int(x + xpos * fd->cosa + p * fd->sina), int(y - xpos * fd->sina + p * fd->cosa), fd->underline_thickness, ink ); } } } else { if(dx) { int xpos = ox; Buffer ch(n); for(int i = 0; i < n; i++) { ch[i].ucs4 = text[i]; ch[i].x = xpos; ch[i].y = oy + fd->ascent; xpos += dx[i]; } XftDrawCharSpec(xftdraw, &c, fd->xftfont, ch, n); } else XftDrawString16(xftdraw, &c, fd->xftfont, ox, oy + fd->ascent, (FcChar16 *)text, n); LLOG("XftColor: r=" << c.color.red << ", g=" << c.color.green << ", b=" << c.color.blue << ", alpha=" << c.color.alpha << ", pixel=" << FormatIntHex(c.pixel)); if(font.IsUnderline() || font.IsStrikeout()) { int cx; if(dx && n > 0) { cx = 0; Sum(cx, dx, dx + n - 1); cx += lastFont[text[n - 1]]; } else cx = GetTextSize(text, font, n).cx; if(font.IsUnderline()) DrawRect(x, y + lastFont.GetAscent() + lastFont.ptr->underline_position, cx, lastFont.ptr->underline_thickness, ink); if(font.IsStrikeout()) DrawRect(x, y + 2 * lastFont.GetAscent() / 3, cx, lastFont.ptr->underline_thickness, ink); } } } #endif END_UPP_NAMESPACE