mirror of
https://github.com/rhaleblian/dslibris.git
synced 2025-06-18 16:55:41 -04:00
915 lines
21 KiB
C++
915 lines
21 KiB
C++
/*
|
|
|
|
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 <iostream>
|
|
#include <sstream>
|
|
#include <sys/param.h>
|
|
|
|
#include "nds.h"
|
|
#include "fat.h"
|
|
#include "string.h"
|
|
|
|
#include "app.h"
|
|
#include "ft.h"
|
|
#include "main.h"
|
|
#include "version.h"
|
|
#include "text.h"
|
|
|
|
extern char msg[];
|
|
std::stringstream ss;
|
|
|
|
void Text::CopyScreen(u16 *src, u16 *dst) {
|
|
memcpy(src, dst, display.width * display.height * sizeof(u16));
|
|
}
|
|
|
|
Text::Text()
|
|
{
|
|
display.height = PAGE_HEIGHT;
|
|
display.width = PAGE_WIDTH;
|
|
filenames[TEXT_STYLE_REGULAR] = FONTREGULARFILE;
|
|
filenames[TEXT_STYLE_BOLD] = FONTBOLDFILE;
|
|
filenames[TEXT_STYLE_ITALIC] = FONTITALICFILE;
|
|
filenames[TEXT_STYLE_BROWSER] = FONTBROWSERFILE;
|
|
filenames[TEXT_STYLE_SPLASH] = FONTSPLASHFILE;
|
|
screenleft = (u16*)BG_BMP_RAM_SUB(0);
|
|
screenright = (u16*)BG_BMP_RAM(0);
|
|
offscreen = new u16[display.width * display.height];
|
|
margin.left = MARGINLEFT;
|
|
margin.right = MARGINRIGHT;
|
|
margin.top = MARGINTOP;
|
|
margin.bottom = MARGINBOTTOM;
|
|
bgcolor.r = 31;
|
|
bgcolor.g = 31;
|
|
bgcolor.b = 15;
|
|
usebgcolor = false;
|
|
invert = false;
|
|
justify = false;
|
|
linespacing = 0;
|
|
ftc = false;
|
|
initialized = false;
|
|
imagetype.face_id = (FTC_FaceID)&face_id;
|
|
imagetype.flags = FT_LOAD_DEFAULT;
|
|
imagetype.height = pixelsize;
|
|
imagetype.width = 0;
|
|
|
|
// Rendering state.
|
|
hit = false;
|
|
linebegan = false;
|
|
codeprev = 0;
|
|
bold = false;
|
|
italic = false;
|
|
face = NULL;
|
|
style = TEXT_STYLE_REGULAR;
|
|
pixelsize = PIXELSIZE;
|
|
screen = screenleft;
|
|
|
|
// Statistics.
|
|
stats_hits = 0;
|
|
stats_misses = 0;
|
|
|
|
ClearScreen(offscreen, 255, 255, 255);
|
|
ss.clear();
|
|
}
|
|
|
|
Text::~Text()
|
|
{
|
|
// framebuffers
|
|
free(offscreen);
|
|
|
|
// homemade cache
|
|
ClearCache();
|
|
for(map<FT_Face, Cache*>::iterator iter = textCache.begin();
|
|
iter != textCache.end(); iter++) {
|
|
delete iter->second;
|
|
}
|
|
textCache.clear();
|
|
|
|
// FreeType
|
|
for (map<u8, FT_Face>::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 );
|
|
}
|
|
|
|
FT_Error Text::InitFreeTypeCache(void) {
|
|
//! Use FreeType's cache manager. borken!
|
|
|
|
auto error = FT_Init_FreeType(&library);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
error = FTC_Manager_New(library,0,0,0,
|
|
&TextFaceRequester,NULL,&cache.manager);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
error = FTC_ImageCache_New(cache.manager,&cache.image);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
error = FTC_SBitCache_New(cache.manager,&cache.sbit);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
error = FTC_CMapCache_New(cache.manager,&cache.cmap);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
|
|
sprintf(face_id.file_path, "%s/%s", FONTDIR, filenames[TEXT_STYLE_REGULAR].c_str());
|
|
face_id.face_index = 0;
|
|
sprintf(msg, "%s %s %d\n", filenames[TEXT_STYLE_REGULAR].c_str(), face_id.file_path, face_id.face_index);
|
|
app->Log(msg);
|
|
error = FTC_Manager_LookupFace(cache.manager, (FTC_FaceID)&face_id, &faces[TEXT_STYLE_REGULAR]);
|
|
if(error) return error;
|
|
app->Log("ok\n");
|
|
// FT_EXPORT( FT_Error )
|
|
// FTC_Manager_LookupSize( FTC_Manager manager,
|
|
// FTC_Scaler scaler,
|
|
// FT_Size *asize );
|
|
|
|
ReportFace(faces[TEXT_STYLE_REGULAR]);
|
|
|
|
// FT_Select_Charmap(GetFace(TEXT_STYLE_REGULAR), FT_ENCODING_UNICODE);
|
|
// charmap_index = FT_Get_Charmap_Index(GetFace(TEXT_STYLE_REGULAR)->charmap);
|
|
|
|
screen = screenleft;
|
|
InitPen();
|
|
initialized = true;
|
|
app->Log("initialized freetype cache\n");
|
|
return 0;
|
|
}
|
|
|
|
FT_Error Text::CreateFace(int style) {
|
|
std::string path = std::string(FONTDIR) + "/" + filenames[style];
|
|
FT_Error err = FT_New_Face(library, path.c_str(), 0, &face);
|
|
if (!err)
|
|
faces[style] = face;
|
|
return err;
|
|
}
|
|
|
|
int Text::InitHomemadeCache(void) {
|
|
//! Use our own cheesey glyph cache.
|
|
FT_Error err;
|
|
|
|
err = FT_Init_FreeType(&library);
|
|
if (err) return err;
|
|
|
|
err = CreateFace(TEXT_STYLE_BROWSER);
|
|
err = CreateFace(TEXT_STYLE_SPLASH);
|
|
err = CreateFace(TEXT_STYLE_REGULAR);
|
|
err = CreateFace(TEXT_STYLE_ITALIC);
|
|
if (err)
|
|
faces[TEXT_STYLE_ITALIC] = faces[TEXT_STYLE_REGULAR];
|
|
err = CreateFace(TEXT_STYLE_BOLD);
|
|
if (err)
|
|
faces[TEXT_STYLE_BOLD] = faces[TEXT_STYLE_REGULAR];
|
|
|
|
std::map<u8, FT_Face>::iterator iter;
|
|
for (iter = faces.begin(); iter != faces.end(); iter++) {
|
|
FT_Set_Pixel_Sizes(iter->second, 0, pixelsize);
|
|
textCache.insert(make_pair(iter->second, new Cache()));
|
|
}
|
|
|
|
screen = screenleft;
|
|
ClearCache();
|
|
InitPen();
|
|
initialized = true;
|
|
app->Log("custom cache initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
int Text::Init()
|
|
{
|
|
if(ftc)
|
|
return InitFreeTypeCache();
|
|
else
|
|
return InitHomemadeCache();
|
|
}
|
|
|
|
void Text::ReportFace(FT_Face face)
|
|
{
|
|
sprintf(msg, "%s\n", face->family_name);
|
|
app->Log(msg);
|
|
sprintf(msg, "%s\n", face->style_name);
|
|
app->Log(msg);
|
|
sprintf(msg, "faces %ld\n", face->num_faces);
|
|
app->Log(msg);
|
|
sprintf(msg, "glyphs %ld\n", face->num_glyphs);
|
|
app->Log(msg);
|
|
sprintf(msg, "fixed-sizes %d\n", face->num_fixed_sizes);
|
|
app->Log(msg);
|
|
for (int i=0;i<face->num_fixed_sizes;i++)
|
|
{
|
|
sprintf(msg, " w %d h %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_REGULAR);
|
|
}
|
|
|
|
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_Select_Charmap(GetFace(TEXT_STYLE_REGULAR), FT_ENCODING_UNICODE);
|
|
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;i<textCache[face]->cachenext;i++)
|
|
if(textCache[face]->cache_ucs[i] == ucs)
|
|
return &textCache[face]->glyphs[i];
|
|
#endif
|
|
|
|
map<u16,FT_GlyphSlot>::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<u8, FT_Face>::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<u16, FT_GlyphSlot>::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<yh; y++) {
|
|
// memcpy((void*)screen[y*display.height+xl],(void*)word,xh-xl/2);
|
|
for(u16 x=xl; x<xh; x++) {
|
|
// FIXME: crashes on hw
|
|
screen[y*display.height+x] = clearcolor;
|
|
}
|
|
}
|
|
}
|
|
|
|
u8 Text::GetStringWidth(const char *txt, u8 style)
|
|
{
|
|
return GetStringWidth(txt, GetFace(style));
|
|
}
|
|
|
|
u8 Text::GetStringWidth(const char *txt, FT_Face face)
|
|
{
|
|
//! Return total advance in pixels.
|
|
u8 width = 0;
|
|
const char *c;
|
|
for(c = txt; c != NULL; c++)
|
|
{
|
|
u32 ucs = 0;
|
|
GetCharCode(c, &ucs);
|
|
width += GetAdvance(ucs, face);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
u8 Text::GetCharCountInsideWidth(const char *txt, u8 style, u8 pixels) {
|
|
u8 n = 0;
|
|
u8 width = 0;
|
|
const char *c;
|
|
for(c = txt; c != NULL; c++)
|
|
{
|
|
u32 ucs = 0;
|
|
GetCharCode(c, &ucs);
|
|
width += GetAdvance(ucs, GetFace(style));
|
|
if (width > pixels) return n;
|
|
n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
u8 Text::GetCharCode(const char *utf8, u32 *ucs) {
|
|
//! Given a UTF-8 encoding, fill in the Unicode/UCS code point.
|
|
//! Return the bytelength of the encoding, for advancing
|
|
//! to the next character; 0 if encoding could not be translated.
|
|
|
|
// TODO - handle 4 byte encodings.
|
|
|
|
if (utf8[0] < 0x80) { // ASCII
|
|
*ucs = utf8[0];
|
|
return 1;
|
|
|
|
} else if (utf8[0] > 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<u8, FT_Face>::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;i<strlen(s);i++) {
|
|
advance += GetAdvance(s[i]);
|
|
}
|
|
return advance;
|
|
}
|
|
|
|
bool Text::GetFontName(std::string &s) {
|
|
const char *name = FT_Get_Postscript_Name(GetFace(TEXT_STYLE_REGULAR));
|
|
if(!name)
|
|
return false;
|
|
else {
|
|
s = name;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void Text::InitPen(void) {
|
|
pen.x = margin.left;
|
|
pen.y = margin.top + GetHeight();
|
|
}
|
|
|
|
void Text::PrintChar(u32 ucs)
|
|
{
|
|
PrintChar(ucs, GetFace(style));
|
|
}
|
|
|
|
void Text::PrintChar(u32 ucs, u8 astyle) {
|
|
PrintChar(ucs, GetFace(astyle));
|
|
}
|
|
|
|
void Text::PrintChar(u32 ucs, FT_Face face) {
|
|
// Draw a character for the given UCS codepoint,
|
|
// into the current screen buffer at the current pen position.
|
|
|
|
// static bool firsttime = true;
|
|
u16 bx, by, width, height = 0;
|
|
FT_Byte *buffer = NULL;
|
|
FT_UInt advance = 0;
|
|
FTC_Node anode = nullptr;
|
|
FT_Glyph glyph;
|
|
|
|
ss.clear();
|
|
|
|
// get metrics and glyph pointer.
|
|
|
|
if(ftc)
|
|
{
|
|
// use the FT cache.
|
|
|
|
auto glyph_index = FTC_CMapCache_Lookup(cache.cmap, (FTC_FaceID)&face_id, -1, ucs);
|
|
error = FTC_ImageCache_Lookup(cache.image, &imagetype, glyph_index, &glyph, &anode);
|
|
if (error) {
|
|
ss << "error " << error << std::endl;
|
|
app->Log(ss.str().c_str());
|
|
return;
|
|
}
|
|
app->Log("ok\n");
|
|
|
|
FTC_SBit p = &sbit;
|
|
error = FTC_SBitCache_Lookup(cache.sbit,
|
|
&imagetype,
|
|
glyph_index,
|
|
&p,
|
|
&anode );
|
|
if (error) {
|
|
ss << "error " << error << std::endl;
|
|
app->Log(ss.str().c_str());
|
|
return;
|
|
}
|
|
app->Log("ok\n");
|
|
|
|
buffer = sbit.buffer;
|
|
bx = sbit.left;
|
|
by = sbit.top;
|
|
height = sbit.height;
|
|
width = sbit.width;
|
|
advance = sbit.xadvance;
|
|
|
|
error = FT_Render_Glyph(faces[TEXT_STYLE_REGULAR]->glyph, /* glyph slot */
|
|
FT_RENDER_MODE_LCD_V); /* render mode */
|
|
if (error) {
|
|
ss << "error " << error << std::endl;
|
|
app->Log(ss.str().c_str());
|
|
return;
|
|
}
|
|
app->Log("ok\n");
|
|
|
|
// auto glyph = faces[TEXT_STYLE_REGULAR]->glyph;
|
|
// buffer = glyph->bitmap.buffer;
|
|
// bx = glyph->bitmap_left;
|
|
// by = glyph->bitmap_top;
|
|
// width = glyph->bitmap.width;
|
|
// height = glyph->bitmap.rows;
|
|
// advance = width;
|
|
|
|
// ss.clear();
|
|
// ss << " err " << error
|
|
// << " glyph_index " << glyph_index << " glyph " << glyph
|
|
// << " width " << width << " height " << height << " advance " << advance
|
|
// << std::endl;
|
|
// app->Log(ss.str());
|
|
}
|
|
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);
|
|
|
|
// ss << "ucs " << ucs << std::endl;
|
|
// app->Log(ss.str());
|
|
|
|
// error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
|
// if (error) app->Log("boo\n");
|
|
// error = FT_Load_Char(face, ucs, FT_LOAD_RENDER|FT_LOAD_TARGET_REGULAR);
|
|
// if (error) app->Log("hoo\n");
|
|
// auto glyph = face->glyph;
|
|
// error = FT_Get_Glyph( face->glyph, &glyph );
|
|
// if (error) app->Log("foo\n");
|
|
|
|
// extract glyph image
|
|
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;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_KERNING
|
|
// Fetch a kerning vector.
|
|
|
|
if(codeprev) {
|
|
FT_Vector kerning_vector;
|
|
std::stringstream ss;
|
|
error = FT_Get_Kerning(face, codeprev, ucs, FT_KERNING_DEFAULT, &kerning_vector);
|
|
#ifdef DEBUG
|
|
if(error) {
|
|
ss << "error: kerning lookup error: " << codeprev << " -> " << ucs << std::endl;
|
|
} else {
|
|
ss << "info : kerning lookup: " << codeprev << " -> " << ucs
|
|
<< " = " << kerning_vector.x << "," << kerning_vector.y << std::endl;
|
|
// pen.x += k.x >> 6;
|
|
}
|
|
app->Log(ss.str());
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// render to framebuffer.
|
|
|
|
#ifdef DEBUG_PEN_POSITION
|
|
// DEBUG Mark the pen position.
|
|
screen[pen.y*display.height+pen.x] = RGB15(0, 0, 0) | BIT(15);
|
|
#endif
|
|
|
|
u16 gx, gy;
|
|
for (gy=0; gy<height; gy++) {
|
|
for (gx=0; gx<width; gx++) {
|
|
u8 a = buffer[gy*width+gx];
|
|
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
|
|
// Draw cache hits in red.
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pen.x += advance;
|
|
codeprev = ucs;
|
|
|
|
// Release the glyph storage.
|
|
if (ftc && anode)
|
|
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(i<strlen((char*)s)) {
|
|
u32 c = s[i];
|
|
if (c == '\n') {
|
|
PrintNewLine();
|
|
i++;
|
|
} else {
|
|
i+=GetCharCode(&(s[i]),&c);
|
|
PrintChar(c, face);
|
|
//clast = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Text::PrintStats() {
|
|
//! Tell log how well we're caching.
|
|
sprintf(msg, "info: %d cache hits.\n", stats_hits);
|
|
app->Log(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(invert);
|
|
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<PAGE_HEIGHT*PAGE_HEIGHT;i++)
|
|
screen[i] = RGB15(r,g,b) | BIT(15);
|
|
}
|
|
|
|
void Text::PrintSplash(u16 *screen)
|
|
{
|
|
u8 size = GetPixelSize();
|
|
u16* s = GetScreen();
|
|
|
|
SetScreen(screen);
|
|
drawstack(screen);
|
|
sprintf(msg,"%s",VERSION);
|
|
PrintStatusMessage(msg);
|
|
|
|
SetPixelSize(size);
|
|
SetInvert(invert);
|
|
SetScreen(s);
|
|
|
|
swiWaitForVBlank();
|
|
}
|
|
|
|
void Text::SetFontFile(const char *filename, u8 style)
|
|
{
|
|
if(!strcmp(filenames[style].c_str(),filename)) return;
|
|
filenames[style] = filename;
|
|
if(initialized) ClearCache(style);
|
|
}
|
|
|
|
string Text::GetFontFile(u8 style)
|
|
{
|
|
return filenames[style];
|
|
}
|
|
|
|
bool Text::SetFace(u8 astyle)
|
|
{
|
|
style = astyle;
|
|
face = faces[style];
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
FT_Face Text::GetFace(u8 style)
|
|
{
|
|
return face;
|
|
|
|
map<u8, FT_Face>::iterator iter = faces.find(style);
|
|
if (iter != faces.end())
|
|
return iter->second;
|
|
else
|
|
return faces[TEXT_STYLE_REGULAR];
|
|
}
|
|
*/
|
|
|
|
int asciiart() {
|
|
auto ft = typesetter();
|
|
auto error = renderer(ft.face);
|
|
free_ft(ft);
|
|
return error;
|
|
}
|
|
|
|
const char* ErrorString(u8 c) {
|
|
switch (c) {
|
|
case 0:
|
|
return "ok";
|
|
break;
|
|
default:
|
|
return "unknown error";
|
|
}
|
|
}
|
|
|
|
// "no error",
|
|
// "cannot open resource" ,
|
|
// "unknown file format" ,
|
|
// "broken file" ,
|
|
// "invalid FreeType version"
|
|
// "module version is too low",
|
|
// "invalid argument"
|
|
// "unimplemented feature"
|
|
// "broken table"
|
|
// "broken offset within table"
|
|
// "array allocation size too large"
|
|
// "missing module"
|
|
// "missing property"
|
|
// ]
|