#include #include #include #include #include #include #include // use this for init Freetype... #include #include FT_FREETYPE_H #include #include #include #include #include #include //#define HAVE_FRIBIDI // until we have it in the cdk #ifdef HAVE_FRIBIDI #include #endif #include fontRenderClass *fontRenderClass::instance; static pthread_mutex_t ftlock=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; static pthread_mutex_t refcntlck=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; static FTC_Font cache_current_font=0; struct fntColorCacheKey { gRGB start, end; fntColorCacheKey(const gRGB &start, const gRGB &end) : start(start), end(end) { } bool operator <(const fntColorCacheKey &c) const { if (start < c.start) return 1; else if (start == c.start) return end < c.end; return 0; } }; std::map colorcache; static gLookup &getColor(const gPalette &pal, const gRGB &start, const gRGB &end) { fntColorCacheKey key(start, end); std::map::iterator i=colorcache.find(key); if (i != colorcache.end()) return i->second; gLookup &n=colorcache.insert(std::pair(key,gLookup())).first->second; eDebug("[FONT] creating new font color cache entry %02x%02x%02x%02x .. %02x%02x%02x%02x", start.a, start.r, start.g, start.b, end.a, end.r, end.g, end.b); n.build(16, pal, start, end); /* for (int i=0; i<16; i++) eDebugNoNewLine("%02x|%02x%02x%02x%02x ", (int)n.lookup[i], pal.data[n.lookup[i]].a, pal.data[n.lookup[i]].r, pal.data[n.lookup[i]].g, pal.data[n.lookup[i]].b); eDebug("");*/ return n; } fontRenderClass *fontRenderClass::getInstance() { return instance; } FT_Error myFTC_Face_Requester(FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face* aface) { return ((fontRenderClass*)request_data)->FTC_Face_Requester(face_id, aface); } FT_Error fontRenderClass::FTC_Face_Requester(FTC_FaceID face_id, FT_Face* aface) { fontListEntry *font=(fontListEntry *)face_id; if (!font) return -1; // eDebug("[FONT] FTC_Face_Requester (%s)", font->face.c_str()); int error; if ((error=FT_New_Face(library, font->filename.c_str(), 0, aface))) { eDebug(" failed: %s", strerror(error)); return error; } FT_Select_Charmap(*aface, ft_encoding_unicode); return 0; } FTC_FaceID fontRenderClass::getFaceID(const eString &face) { for (fontListEntry *f=font; f; f=f->next) { if (f->face == face) return (FTC_FaceID)f; } return 0; } FT_Error fontRenderClass::getGlyphBitmap(FTC_Image_Desc *font, FT_ULong glyph_index, FTC_SBit *sbit) { FT_Error res=FTC_SBit_Cache_Lookup(sbitsCache, font, glyph_index, sbit); return res; } eString fontRenderClass::AddFont(const eString &filename, const eString &name, int scale) { eDebugNoNewLine("[FONT] adding font %s...", filename.c_str()); fflush(stdout); int error; fontListEntry *n=new fontListEntry; n->scale=scale; FT_Face face; singleLock s(ftlock); if ((error=FT_New_Face(library, filename.c_str(), 0, &face))) eFatal(" failed: %s", strerror(error)); n->filename=filename; n->face=name; FT_Done_Face(face); n->next=font; eDebug("OK (%s)", n->face.c_str()); font=n; return n->face; } fontRenderClass::fontListEntry::~fontListEntry() { } fontRenderClass::fontRenderClass(): fb(fbClass::getInstance()) { instance=this; eDebug("[FONT] initializing lib..."); { if (FT_Init_FreeType(&library)) { eDebug("[FONT] initializing failed."); return; } } eDebug("[FONT] loading fonts..."); fflush(stdout); font=0; int maxbytes=4*1024*1024; eDebug("[FONT] Intializing font cache, using max. %dMB...", maxbytes/1024/1024); fflush(stdout); { if (FTC_Manager_New(library, 8, 8, maxbytes, myFTC_Face_Requester, this, &cacheManager)) { eDebug("[FONT] initializing font cache failed!"); return; } if (!cacheManager) { eDebug("[FONT] initializing font cache manager error."); return; } if (FTC_SBit_Cache_New(cacheManager, &sbitsCache)) { eDebug("[FONT] initializing font cache sbit failed!"); return; } if (FTC_Image_Cache_New(cacheManager, &imageCache)) { eDebug("[FONT] initializing font cache imagecache failed!"); } } return; } float fontRenderClass::getLineHeight(const gFont& font) { if (!instance) return 0; Font *fnt = getFont( font.family.c_str(), font.pointSize); if (!fnt) return 0; singleLock s(ftlock); FT_Face current_face; if (FTC_Manager_Lookup_Size(cacheManager, &fnt->font.font, ¤t_face, &fnt->size)<0) { eDebug("FTC_Manager_Lookup_Size failed!"); return 0; } int linegap=current_face->size->metrics.height-(current_face->size->metrics.ascender+current_face->size->metrics.descender); float height=(current_face->size->metrics.ascender+current_face->size->metrics.descender+linegap/2.0)/64; delete fnt; return height; } fontRenderClass::~fontRenderClass() { singleLock s(ftlock); // auskommentiert weil freetype und enigma die kritische masse des suckens ueberschreiten. // FTC_Manager_Done(cacheManager); // FT_Done_FreeType(library); } Font *fontRenderClass::getFont(const eString &face, int size, int tabwidth) { FTC_FaceID id=getFaceID(face); if (!id) return 0; return new Font(this, id, size * ((fontListEntry*)id)->scale / 100, tabwidth); } Font::Font(fontRenderClass *render, FTC_FaceID faceid, int isize, int tw): tabwidth(tw) { renderer=render; font.font.face_id=faceid; font.font.pix_width = isize; font.font.pix_height = isize; font.image_type = ftc_image_grays; height=isize; if (tabwidth==-1) tabwidth=8*isize; ref=0; // font.image_type |= ftc_image_flag_autohinted; } FT_Error Font::getGlyphBitmap(FT_ULong glyph_index, FTC_SBit *sbit) { return renderer->getGlyphBitmap(&font, glyph_index, sbit); } Font::~Font() { } void Font::lock() { ref++; } void Font::unlock() { ref--; if (!ref) delete this; } int eTextPara::appendGlyph(Font *current_font, FT_Face current_face, FT_UInt glyphIndex, int flags, int rflags) { FTC_SBit glyph; if (current_font->getGlyphBitmap(glyphIndex, &glyph)) return 1; int nx=cursor.x(); nx+=glyph->xadvance; if ( (rflags&RS_WRAP) && (nx >= area.right()) ) { int cnt = 0; glyphString::iterator i(glyphs.end()); --i; while (i != glyphs.begin()) { if (i->flags&(GS_ISSPACE|GS_ISFIRST)) break; cnt++; --i; } if (i != glyphs.begin() && ((i->flags&(GS_ISSPACE|GS_ISFIRST))==GS_ISSPACE) && (++i != glyphs.end())) // skip space { int linelength=cursor.x()-i->x; i->flags|=GS_ISFIRST; ePoint offset=ePoint(i->x, i->y); newLine(rflags); offset-=cursor; while (i != glyphs.end()) // rearrange them into the next line { i->x-=offset.x(); i->y-=offset.y(); i->bbox.moveBy(-offset.x(), -offset.y()); ++i; } cursor+=ePoint(linelength, 0); // put the cursor after that line } else { if (cnt) { newLine(rflags); flags|=GS_ISFIRST; } } } int xadvance=glyph->xadvance, kern=0; if (previous && use_kerning) { FT_Vector delta; FT_Get_Kerning(current_face, previous, glyphIndex, ft_kerning_default, &delta); kern=delta.x>>6; } pGlyph ng; ng.bbox.setLeft( (flags&GS_ISFIRST|glyphs.empty()?cursor.x():cursor.x()-1) + glyph->left ); ng.bbox.setTop( cursor.y() - glyph->top ); ng.bbox.setWidth( glyph->width ); ng.bbox.setHeight( glyph->height ); xadvance+=kern; ng.x=cursor.x()+kern; ng.y=cursor.y(); ng.w=xadvance; ng.font=current_font; ng.font->lock(); ng.glyph_index=glyphIndex; ng.flags=flags; glyphs.push_back(ng); cursor+=ePoint(xadvance, 0); previous=glyphIndex; return 0; } void eTextPara::calc_bbox() { boundBox.setLeft( 32000 ); boundBox.setTop( 32000 ); boundBox.setRight( -32000 ); // for each glyph image, compute its bounding box, translate it, boundBox.setBottom( -32000 ); // and grow the string bbox for ( glyphString::iterator i(glyphs.begin()); i != glyphs.end(); ++i) { if ( i->bbox.left() < boundBox.left() ) boundBox.setLeft( i->bbox.left() ); if ( i->bbox.top() < boundBox.top() ) boundBox.setTop( i->bbox.top() ); if ( i->bbox.right() > boundBox.right() ) boundBox.setRight( i->bbox.right() ); if ( i->bbox.bottom() > boundBox.bottom() ) boundBox.setBottom( i->bbox.bottom() ); } // eDebug("boundBox left = %i, top = %i, right = %i, bottom = %i", boundBox.left(), boundBox.top(), boundBox.right(), boundBox.bottom() ); bboxValid=1; } void eTextPara::newLine(int flags) { if (maximum.width()size->metrics.height-(current_face->size->metrics.ascender+current_face->size->metrics.descender); cursor+=ePoint(0, (current_face->size->metrics.ascender+current_face->size->metrics.descender+linegap*1/2)>>6); if (maximum.height()=0) eFatal("verdammt man der war noch gelockt :/\n"); } void eTextPara::destroy() { singleLock s(refcntlck); if (!refcnt--) delete this; } eTextPara *eTextPara::grab() { singleLock s(refcntlck); refcnt++; return this; } void eTextPara::setFont(const gFont &font) { if (refcnt) eFatal("mod. after lock"); Font *fnt=fontRenderClass::getInstance()->getFont(font.family.c_str(), font.pointSize); if (!fnt) eWarning("FONT '%s' MISSING!", font.family.c_str()); setFont(fnt, fontRenderClass::getInstance()->getFont(replacement_facename.c_str(), font.pointSize)); } eString eTextPara::replacement_facename; void eTextPara::setFont(Font *fnt, Font *replacement) { if (refcnt) eFatal("mod. after lock"); if (!fnt) return; if (current_font && !current_font->ref) delete current_font; current_font=fnt; replacement_font=replacement; singleLock s(ftlock); // we ask for replacment_font first becauseof the cache if (replacement_font) { if (FTC_Manager_Lookup_Size(fontRenderClass::instance->cacheManager, &replacement_font->font.font, &replacement_face, &replacement_font->size)<0) { eDebug("FTC_Manager_Lookup_Size failed!"); return; } } if (current_font) { if (FTC_Manager_Lookup_Size(fontRenderClass::instance->cacheManager, ¤t_font->font.font, ¤t_face, ¤t_font->size)<0) { eDebug("FTC_Manager_Lookup_Size failed!"); return; } } cache_current_font=¤t_font->font.font; previous=0; use_kerning=FT_HAS_KERNING(current_face); } void shape (std::vector &string, const std::vector &text); int eTextPara::renderString(const eString &string, int rflags) { singleLock s(ftlock); if (refcnt) eFatal("mod. after lock"); if (!current_font) return -1; if (cursor.y()==-1) { cursor=ePoint(area.x(), area.y()+(current_face->size->metrics.ascender>>6)); left=cursor.x(); } if (¤t_font->font.font != cache_current_font) { if (FTC_Manager_Lookup_Size(fontRenderClass::instance->cacheManager, ¤t_font->font.font, ¤t_face, ¤t_font->size)<0) { eDebug("FTC_Manager_Lookup_Size failed!"); return -1; } cache_current_font=¤t_font->font.font; } std::vector uc_string, uc_visual; uc_string.reserve(string.length()); std::string::const_iterator p(string.begin()); while(p != string.end()) { unsigned int unicode=*p++; if (unicode & 0x80) // we have (hopefully) UTF8 here, and we assume that the encoding is VALID { if ((unicode & 0xE0)==0xC0) // two bytes { unicode&=0x1F; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; } else if ((unicode & 0xF0)==0xE0) // three bytes { unicode&=0x0F; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; } else if ((unicode & 0xF8)==0xF0) // four bytes { unicode&=0x07; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; unicode<<=6; if (p != string.end()) unicode|=(*p++)&0x3F; } } uc_string.push_back(unicode); } std::vector uc_shape; // character -> glyph conversion shape(uc_shape, uc_string); // now do the usual logical->visual reordering #ifdef HAVE_FRIBIDI FriBidiCharType dir=FRIBIDI_TYPE_ON; { int size=uc_shape.size(); uc_visual.resize(size); // gaaanz lahm, aber anders geht das leider nicht, sorry. FriBidiChar array[size], target[size]; std::copy(uc_shape.begin(), uc_shape.end(), array); fribidi_log2vis(array, size, &dir, target, 0, 0, 0); uc_visual.assign(target, target+size); } #else uc_visual=uc_shape; #endif glyphs.reserve(uc_visual.size()); for (std::vector::const_iterator i(uc_visual.begin()); i != uc_visual.end(); ++i) { int isprintable=1; int flags=0; if (!(rflags&RS_DIRECT)) { switch (*i) { case '\\': { unsigned long c = *(i+1); switch (c) { case 'n': i++; goto newline; case 't': i++; goto tab; case 'r': i++; goto nprint; default: ; } break; } case '\t': tab: isprintable=0; cursor+=ePoint(current_font->tabwidth, 0); cursor-=ePoint(cursor.x()%current_font->tabwidth, 0); break; case 0x8A: case 0xE08A: case '\n': newline:isprintable=0; newLine(rflags); flags|=GS_ISFIRST; break; case '\r': case 0x86: case 0xE086: case 0x87: case 0xE087: nprint: isprintable=0; break; case ' ': flags|=GS_ISSPACE; default: break; } } if (isprintable) { FT_UInt index; index=(rflags&RS_DIRECT)? *i : FT_Get_Char_Index(current_face, *i); if (!index) { if (replacement_face) index=(rflags&RS_DIRECT)? *i : FT_Get_Char_Index(replacement_face, *i); if (!index) eDebug("unicode %d ('%c') not present", *i, *i); else appendGlyph(replacement_font, replacement_face, index, flags, rflags); } else appendGlyph(current_font, current_face, index, flags, rflags); } } bboxValid=false; calc_bbox(); #ifdef HAVE_FRIBIDI if (dir & FRIBIDI_MASK_RTL) realign(dirRight); #endif return 0; } void eTextPara::blit(gPixmapDC &dc, const ePoint &offset, const gRGB &background, const gRGB &foreground) { singleLock s(ftlock); if (!current_font) return; if (¤t_font->font.font != cache_current_font) { if (FTC_Manager_Lookup_Size(fontRenderClass::instance->cacheManager, ¤t_font->font.font, ¤t_face, ¤t_font->size)<0) { eDebug("FTC_Manager_Lookup_Size failed!"); return; } cache_current_font=¤t_font->font.font; } ePtr target; dc.getPixmap(target); register int opcode; gColor *lookup8=0; __u32 lookup32[16]; if (target->bpp == 8) { if (target->clut.data) { lookup8=getColor(target->clut, background, foreground).lookup; opcode=0; } else opcode=1; } else if (target->bpp == 32) { opcode=3; if (target->clut.data) { lookup8=getColor(target->clut, background, foreground).lookup; for (int i=0; i<16; ++i) lookup32[i]=((target->clut.data[lookup8[i]].a<<24)| (target->clut.data[lookup8[i]].r<<16)| (target->clut.data[lookup8[i]].g<<8)| (target->clut.data[lookup8[i]].b))^0xFF000000; } else { for (int i=0; i<16; ++i) lookup32[i]=(0x010101*i)|0xFF000000; } } else { eWarning("can't render to %dbpp", target->bpp); return; } eRect clip(0, 0, target->x, target->y); clip&=dc.getClip(); int buffer_stride=target->stride; for (glyphString::iterator i(glyphs.begin()); i != glyphs.end(); ++i) { static FTC_SBit glyph_bitmap; if (fontRenderClass::instance->getGlyphBitmap(&i->font->font, i->glyph_index, &glyph_bitmap)) continue; int rx=i->x+glyph_bitmap->left + offset.x(); int ry=i->y-glyph_bitmap->top + offset.y(); __u8 *d=(__u8*)(target->data)+buffer_stride*ry+rx*target->bypp; __u8 *s=glyph_bitmap->buffer; register int sx=glyph_bitmap->width; int sy=glyph_bitmap->height; if ((sy+ry) >= clip.bottom()) sy=clip.bottom()-ry; if ((sx+rx) >= clip.right()) sx=clip.right()-rx; if (rx < clip.left()) { int diff=clip.left()-rx; s+=diff; sx-=diff; rx+=diff; d+=diff*target->bypp; } if (ry < clip.top()) { int diff=clip.top()-ry; s+=diff*glyph_bitmap->pitch; sy-=diff; ry+=diff; d+=diff*buffer_stride; } if (sx>0) for (int ay=0; ay>4; if(b) *td++=lookup8[b]; else td++; } } else if (opcode == 1) // 8bit direct { register __u8 *td=d; register int ax; for (ax=0; ax>4; if(b) *td++=lookup32[b]; else td++; } } s+=glyph_bitmap->pitch-sx; d+=buffer_stride; } } } void eTextPara::realign(int dir) // der code hier ist ein wenig merkwuerdig. { glyphString::iterator begin(glyphs.begin()), c(glyphs.begin()), end(glyphs.begin()), last; if (dir==dirLeft) return; while (c != glyphs.end()) { int linelength=0; int numspaces=0, num=0; begin=end; ASSERT( end != glyphs.end()); // zeilenende suchen do { last=end; ++end; } while ((end != glyphs.end()) && (!(end->flags&GS_ISFIRST))); // end zeigt jetzt auf begin der naechsten zeile for (c=begin; c!=end; ++c) { // space am zeilenende skippen if ((c==last) && (c->flags&GS_ISSPACE)) continue; if (c->flags&GS_ISSPACE) numspaces++; linelength+=c->w; num++; } if (!num) // line mit nur einem space continue; switch (dir) { case dirRight: case dirCenter: { int offset=area.width()-linelength; if (dir==dirCenter) offset/=2; offset+=area.left(); while (begin != end) { begin->bbox.moveBy(offset-begin->x,0); begin->x=offset; offset+=begin->w; ++begin; } break; } case dirBlock: { if (end == glyphs.end()) // letzte zeile linksbuendig lassen continue; int spacemode; if (numspaces) spacemode=1; else spacemode=0; if ((!spacemode) && (num<2)) break; int off=(area.width()-linelength)*256/(spacemode?numspaces:(num-1)); int curoff=0; while (begin != end) { int doadd=0; if ((!spacemode) || (begin->flags&GS_ISSPACE)) doadd=1; begin->x+=curoff>>8; begin->bbox.moveBy(curoff>>8,0); if (doadd) curoff+=off; ++begin; } break; } } } bboxValid=false; calc_bbox(); } void eTextPara::clear() { singleLock s(ftlock); for (glyphString::iterator i(glyphs.begin()); i!=glyphs.end(); ++i) i->font->unlock(); glyphs.clear(); } eAutoInitP0 init_fontRenderClass(eAutoInitNumbers::graphic-1, "Font Render Class");