/* dslibris - an ebook reader for the Nintendo DS. Copyright (C) 2007-2008 Ray Haleblian This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include "Text.h" #include "App.h" #include "main.h" #include "version.h" Text::Text() { bold = false; bgcolor.r = 31; bgcolor.g = 31; bgcolor.b = 15; usebgcolor = false; codeprev = 0; face = NULL; filenames[TEXT_STYLE_NORMAL] = FONTFILEPATH; filenames[TEXT_STYLE_BOLD] = FONTBOLDFILEPATH; filenames[TEXT_STYLE_ITALIC] = FONTITALICFILEPATH; filenames[TEXT_STYLE_BROWSER] = FONTBROWSERFILEPATH; filenames[TEXT_STYLE_SPLASH] = FONTSPLASHFILEPATH; ftc = false; invert = false; italic = false; justify = false; linebegan = false; linespacing = 0; pixelsize = PIXELSIZE; screenleft = (u16*)BG_BMP_RAM_SUB(0); screenright = (u16*)BG_BMP_RAM(0); screen = screenleft; style = TEXT_STYLE_NORMAL; margin.left = MARGINLEFT; margin.right = MARGINRIGHT; margin.top = MARGINTOP; margin.bottom = MARGINBOTTOM; display.height = PAGE_HEIGHT; display.width = PAGE_WIDTH; stats_hits = 0; stats_misses = 0; hit = false; initialized = false; } Text::~Text() { ClearCache(); for(map::iterator iter = textCache.begin(); iter != textCache.end(); iter++) { delete iter->second; } textCache.clear(); for (map::iterator iter = faces.begin(); iter != faces.end(); iter++) { FT_Done_Face(iter->second); } FT_Done_FreeType(library); } static FT_Error TextFaceRequester( FTC_FaceID face_id, FT_Library library, FT_Pointer request_data, FT_Face *aface ) { TextFace face = (TextFace) face_id; // simple typecase return FT_New_Face( library, face->file_path, face->face_index, aface ); } int Text::InitWithCacheManager(void) { //! Use FreeType's cache manager. borken! char msg[256]; app->Log("initializing FTC manager.\n"); int error = FT_Init_FreeType(&library); if(error) return error; FTC_Manager_New(library,0,0,0, &TextFaceRequester,NULL,&cache.manager); FTC_ImageCache_New(cache.manager,&cache.image); FTC_SBitCache_New(cache.manager,&cache.sbit); FTC_CMapCache_New(cache.manager,&cache.cmap); face_id.file_path = filenames[TEXT_STYLE_NORMAL].c_str(); face_id.face_index = 0; sprintf(msg, "looking up %s\n", face_id.file_path); app->Log(msg); error = FTC_Manager_LookupFace(cache.manager, (FTC_FaceID)&face_id, &faces[TEXT_STYLE_NORMAL]); if(error) { app->Log("failed!\n"); return error; } ReportFace(faces[TEXT_STYLE_NORMAL]); FT_Select_Charmap(GetFace(TEXT_STYLE_NORMAL), FT_ENCODING_UNICODE); charmap_index = FT_Get_Charmap_Index(GetFace(TEXT_STYLE_NORMAL)->charmap); imagetype.face_id = (FTC_FaceID)&face_id; imagetype.height = pixelsize; imagetype.width = pixelsize; screen = screenleft; InitPen(); ftc = true; initialized = true; return 0; } int Text::InitDefault(void) { //! Use our own cheesey glyph cache. if (FT_Init_FreeType(&library)) return 1; map::iterator iter; for (iter = filenames.begin(); iter != filenames.end(); iter++) { if (FT_New_Face(library, iter->second.c_str(), 0, &face)) { // Failed; attempt to use the NORMAL style map::iterator find = faces.find(TEXT_STYLE_NORMAL); if (find == faces.end()) return 2; else face = find->second; } char msg[MAXPATHLEN]; strcpy(msg, ""); sprintf(msg,"info : font '%s'\n", iter->second.c_str()); app->Log(msg); //FT_Select_Charmap(face, FT_ENCODING_UNICODE); FT_Set_Pixel_Sizes(face, 0, pixelsize); textCache.insert(make_pair(face, new Cache())); faces[iter->first] = face; ReportFace(face); } screen = screenleft; ClearCache(); InitPen(); initialized = true; return 0; } int Text::Init() { if(ftc) return InitWithCacheManager(); else return InitDefault(); } void Text::ReportFace(FT_Face face) { char msg[256]; sprintf(msg, "%ld faces\n", face->num_faces); app->Log(msg); sprintf(msg, "%ld glyphs\n", face->num_glyphs); app->Log(msg); sprintf(msg, "%d sizes\n", face->num_fixed_sizes); app->Log(msg); for (int i=0;inum_fixed_sizes;i++) { sprintf(msg, "%d %d\n", face->available_sizes[i].width, face->available_sizes[i].height); app->Log(msg); } } void Text::Begin() { bold = false; italic = false; linebegan = false; stats_hits = 0; stats_misses = 0; hit = false; } void Text::End() {} int Text::CacheGlyph(u32 ucs) { return CacheGlyph(ucs, TEXT_STYLE_NORMAL); } int Text::CacheGlyph(u32 ucs, u8 style) { return CacheGlyph(ucs, GetFace(style)); } int Text::CacheGlyph(u32 ucs, FT_Face face) { //! Cache glyph at ucs if there's space. //! Does not check if this is a duplicate entry; //! The caller should have checked first. if(textCache[face]->cacheMap.size() == CACHESIZE) return -1; FT_Load_Char(face, ucs, FT_LOAD_RENDER|FT_LOAD_TARGET_NORMAL); FT_GlyphSlot src = face->glyph; // TODO - why? //FT_GlyphSlot dst = &textCache[face]->glyphs[textCache[face]->cachenext]; FT_GlyphSlot dst = new FT_GlyphSlotRec; int x = src->bitmap.rows; int y = src->bitmap.width; dst->bitmap.buffer = new unsigned char[x*y]; memcpy(dst->bitmap.buffer, src->bitmap.buffer, x*y); dst->bitmap.rows = src->bitmap.rows; dst->bitmap.width = src->bitmap.width; dst->bitmap_top = src->bitmap_top; dst->bitmap_left = src->bitmap_left; dst->advance = src->advance; //textCache[face]->cache_ucs[textCache[face]->cachenext] = ucs; textCache[face]->cacheMap.insert(make_pair(ucs, dst)); //textCache[face]->cachenext++; //return textCache[face]->cachenext-1; return ucs; } FT_UInt Text::GetGlyphIndex(u32 ucs) { //! Given a UCS codepoint, return where it is in the charmap, by index. //! Only has effect when FT cache mode is enabled, //! and FT cache mode is borken. if(!ftc) return ucs; return FTC_CMapCache_Lookup(cache.cmap,(FTC_FaceID)&face_id, charmap_index,ucs); } int Text::GetGlyphBitmap(u32 ucs, FTC_SBit *sbit, FTC_Node *anode) { //! Given a UCS code, fills sbit and anode. //! Returns nonzero on error. imagetype.flags = FT_LOAD_DEFAULT|FT_LOAD_RENDER; return FTC_SBitCache_Lookup(cache.sbit,&imagetype, GetGlyphIndex(ucs),sbit,anode); } FT_GlyphSlot Text::GetGlyph(u32 ucs, int flags) { return GetGlyph(ucs, flags, face); } FT_GlyphSlot Text::GetGlyph(u32 ucs, int flags, u8 style) { return GetGlyph(ucs, flags, GetFace(style)); } FT_GlyphSlot Text::GetGlyph(u32 ucs, int flags, FT_Face face) { if(ftc) return NULL; #if 0 for(int i=0;icachenext;i++) if(textCache[face]->cache_ucs[i] == ucs) return &textCache[face]->glyphs[i]; #endif map::iterator iter = textCache[face]->cacheMap.find(ucs); if (iter != textCache[face]->cacheMap.end()) { stats_hits++; hit = true; return iter->second; } stats_misses++; hit = false; int i = CacheGlyph(ucs, face); if (i > -1) return textCache[face]->cacheMap[ucs]; FT_Load_Char(face, ucs, flags); return face->glyph; } void Text::ClearCache() { for (map::iterator iter = faces.begin(); iter != faces.end(); iter++) { ClearCache(iter->second); } } void Text::ClearCache(u8 style) { ClearCache(GetFace(style)); } void Text::ClearCache(FT_Face face) { //textCache[face]->cachenext = 0; map::iterator iter; for(iter = textCache[face]->cacheMap.begin(); iter != textCache[face]->cacheMap.end(); iter++) { delete iter->second; } textCache[face]->cacheMap.clear(); } void Text::ClearScreen() { if(invert) memset((void*)screen,0,PAGE_WIDTH*PAGE_HEIGHT*4); else memset((void*)screen,255,PAGE_WIDTH*PAGE_HEIGHT*4); } void Text::ClearRect(u16 xl, u16 yl, u16 xh, u16 yh) { u16 clearcolor; if(invert) clearcolor = RGB15(0,0,0) | BIT(15); else clearcolor = RGB15(31,31,31) | BIT(15); //uint word = (clearcolor << 16) | clearcolor; for(u16 y=yl; y 0xc1 && utf8[0] < 0xe0) { // latin *ucs = ((utf8[0]-192)*64) + (utf8[1]-128); return 2; } else if (utf8[0] > 0xdf && utf8[0] < 0xf0) { // asian *ucs = (utf8[0]-224)*4096 + (utf8[1]-128)*64 + (utf8[2]-128); return 3; } else if (utf8[0] > 0xef) { // rare return 4; } return 0; } u8 Text::GetHeight() { return (GetFace(style)->size->metrics.height >> 6); } void Text::GetPen(u16 *x, u16 *y) { *x = pen.x; *y = pen.y; } void Text::SetPen(u16 x, u16 y) { pen.x = x; pen.y = y; } void Text::GetPen(u16 &x, u16 &y) { x = pen.x; y = pen.y; } void Text::SetInvert(bool state) { invert = state; } bool Text::GetInvert() { return invert; } u8 Text::GetPenX() { return pen.x; } u8 Text::GetPenY() { return pen.y; } u8 Text::GetPixelSize() { return pixelsize; } u16* Text::GetScreen() { return screen; } void Text::SetPixelSize(u8 size) { if(ftc) { imagetype.height = size; imagetype.width = size; pixelsize = size; return; } for (map::iterator iter = faces.begin(); iter != faces.end(); iter++) { if (!size) FT_Set_Pixel_Sizes(iter->second, 0, PIXELSIZE); else FT_Set_Pixel_Sizes(iter->second, 0, size); } ClearCache(); } void Text::SetScreen(u16 *inscreen) { screen = inscreen; } u8 Text::GetAdvance(u32 ucs) { return GetAdvance(ucs, GetFace(style)); } u8 Text::GetAdvance(u32 ucs, u8 astyle) { return GetAdvance(ucs, GetFace(astyle)); } u8 Text::GetAdvance(u32 ucs, FT_Face face) { //! Return glyph advance in pixels. //! All other flavours of GetAdvance() call this one. // FT_Fixed padvance; // error = FT_Get_Advance(face, GetGlyphIndex(ucs), NULL, &padvance); // return padvance >> 6; if(!ftc) // Caches this glyph if possible. return GetGlyph(ucs, FT_LOAD_DEFAULT, face)->advance.x >> 6; imagetype.flags = FT_LOAD_DEFAULT | FT_LOAD_NO_BITMAP; #if 0 error = FTC_SBitCache_Lookup(cache.sbit,&imagetype, GetGlyphIndex(ucs),&sbit,NULL); return sbit->xadvance; #endif FT_Glyph glyph; FTC_ImageType type = &imagetype; error = FTC_ImageCache_Lookup(cache.image,type,GetGlyphIndex(ucs),&glyph,NULL); return (glyph->advance).x; } int Text::GetStringAdvance(const char *s) { int advance = 0; for(unsigned int i=0;ibuffer; bx = sbit->left; by = sbit->top; height = sbit->height; width = sbit->width; advance = sbit->xadvance; } else { // Consult the cache for glyph data and cache it on a miss // if space is available. FT_GlyphSlot glyph = GetGlyph(ucs, FT_LOAD_RENDER|FT_LOAD_TARGET_NORMAL, face); FT_Bitmap bitmap = glyph->bitmap; bx = glyph->bitmap_left; by = glyph->bitmap_top; width = bitmap.width; height = bitmap.rows; advance = glyph->advance.x >> 6; buffer = bitmap.buffer; } //sprintf(msg, "%ld %d %d %d %d %d\n", ucs, bx, by, width, height, advance); //app->Log(msg); #ifdef EXPERIMENTAL_KERNING // kern. if(codeprev) { FT_Vector k; error = FT_Get_Kerning(face,codeprev,ucs,FT_KERNING_UNSCALED,&k); if(error) { app->Log("warn : kerning lookup error "); app->Log((char *)&codeprev); app->Log("->"); app->Log((char *)&ucs); app->Log("\n"); } else if (k.x) { app->Log("info : kern "); app->Log((int)k.x); app->Log(" "); app->Log((char *)&codeprev); app->Log((char *)&ucs); app->Log("\n"); pen.x += k.x >> 6; } } #endif // render to framebuffer. u16 gx, gy; for (gy=0; gyLog(msg); if (a) { u16 sx = (pen.x+gx+bx); u16 sy = (pen.y+gy-by); if(usebgcolor) { u32 r,g,b; u8 alpha = 255-a; r = (bgcolor.r * alpha); g = (bgcolor.g * alpha); b = (bgcolor.b * alpha); screen[sy*display.height+sx] = RGB15(r/256,g/256,b/256) | BIT(15); } else { u8 l; if (invert) l = a >> 3; else l = (255-a) >> 3; #ifdef DEBUG_CACHE if(!hit) screen[sy*display.height+sx] = RGB15(l,0,0) | BIT(15); else #endif screen[sy*display.height+sx] = RGB15(l,l,l) | BIT(15); } } } //app->Log("\n"); } pen.x += advance; codeprev = ucs; if (ftc) FTC_Node_Unref(anode,cache.manager); } bool Text::PrintNewLine(void) { //! Render a newline at the current position. pen.x = margin.left; int height = GetHeight(); int y = pen.y + height + linespacing; if (y > (display.height - margin.bottom)) { if (screen == screenleft) { screen = screenright; pen.y = margin.top + height; return true; } else return false; } else { pen.y += height + linespacing; return true; } } void Text::PrintString(const char *s) { //! Render a character string starting at the pen position. PrintString(s, TEXT_STYLE_BROWSER); } void Text::PrintString(const char *s, u8 style) { //! Render a character string starting at the pen position. PrintString(s, GetFace(style)); } void Text::PrintString(const char *s, FT_Face face) { //! Render a character string starting at the pen position. //app->Log(s); //u32 clast = 0; u8 i=0; while(iLog(msg); sprintf(msg, "info: %d cache misses.\n", stats_misses); app->Log(msg); } void Text::PrintStatusMessage(const char *msg) { //! Render a one-liner message on the left screen. u16 x,y; GetPen(&x,&y); u16 *s = screen; int ps = GetPixelSize(); bool invert = GetInvert(); screen = screenleft; SetInvert(false); SetPixelSize(10); SetPen(16, PAGE_HEIGHT-32); PrintString(msg); SetInvert(invert); SetPixelSize(ps); screen = s; SetPen(x,y); } void Text::ClearScreen(u16 *screen, u8 r, u8 g, u8 b) { for (int i=0;i::iterator iter = faces.find(style); if (iter != faces.end()) return iter->second; else return faces[TEXT_STYLE_NORMAL]; } */