efl/src/bin/evas/evas_cserve2_fonts.c

637 lines
16 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef DEBUG_LOAD_TIME
#include <sys/time.h>
#endif
#include <Eet.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_SIZES_H
#include FT_TYPES_H
#include FT_MODULE_H
#include FT_OUTLINE_H
#include FT_SYNTHESIS_H
#include "evas_cserve2.h"
#define CACHESIZE 4 * 1024
/* The tangent of the slant angle we do on runtime. This value was
* retrieved from engines/common/evas_font.h */
#define _EVAS_FONT_SLANT_TAN 0.221694663
#define CHECK_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
#define MIN_GLYPHS 50
#define MAX_CACHE_SIZE 1 * 1024 * 1024 // 1MB
#define EVAS_FONT_ROUND_26_6_TO_INT(x) \
(((x + 0x20) & -0x40) >> 6)
static FT_Library cserve2_ft_lib = 0;
static int initialised = 0;
typedef struct _Font_Info Font_Info;
typedef struct _Font_Source_Info Font_Source_Info;
struct _Font_Info
{
Font_Source_Info *fsi;
FT_Size size;
int real_size; // this is probably useless, not used even on client
int fsize;
int dpi;
int max_h;
unsigned int runtime_rend;
int shmsize;
};
struct _Font_Source_Info
{
FT_Face face;
int orig_upem;
int current_size;
int current_dpi;
void *data;
int datasize;
};
static void *
_font_slave_error_send(Error_Type error)
{
Error_Type *e = calloc(1, sizeof(*e));
*e = error;
return e;
}
static void
_font_slave_size_use(Font_Info *fi)
{
Font_Source_Info *fsi = fi->fsi;
// if ((fsi->current_size != fi->fsize)
// || (fsi->current_dpi != fi->dpi))
{
FT_Activate_Size(fi->size);
fsi->current_size = fi->fsize;
fsi->current_dpi = fi->dpi;
}
}
static Font_Source_Info *
_font_slave_source_load(const char *file, const char *name)
{
int error;
Font_Source_Info *fsi = calloc(1, sizeof(*fsi));
if (!name)
{
error = FT_New_Face(cserve2_ft_lib, file, 0, &(fsi->face));
if (error)
{
free(fsi);
return NULL;
}
}
else
{
Eet_File *ef;
void *fdata;
int fsize = 0;
ef = eet_open(file, EET_FILE_MODE_READ);
if (!ef)
{
free(fsi);
return NULL;
}
fdata = eet_read(ef, name, &fsize);
eet_close(ef);
if (!fdata)
{
free(fsi);
return NULL;
}
fsi->data = fdata;
fsi->datasize = fsize;
error = FT_New_Memory_Face(cserve2_ft_lib, fsi->data, fsi->datasize,
0, &(fsi->face));
if (error)
{
free(fsi->data);
free(fsi);
return NULL;
}
}
error = FT_Select_Charmap(fsi->face, ft_encoding_unicode);
if (error)
{
FT_Done_Face(fsi->face);
free(fsi->data);
free(fsi);
return NULL;
}
fsi->orig_upem = fsi->face->units_per_EM;
fsi->current_size = 0;
fsi->current_dpi = 0;
return fsi;
}
static Font_Info *
_font_slave_int_load(const Slave_Msg_Font_Load *msg, Font_Source_Info *fsi)
{
int error;
int val, dv;
int ret;
Font_Info *fi = calloc(1, sizeof(*fi));
error = FT_New_Size(fsi->face, &(fi->size));
if (!error)
FT_Activate_Size(fi->size);
fi->fsize = msg->size;
fi->dpi = msg->dpi;
fi->real_size = msg->size * 64;
fi->fsi = fsi;
error = FT_Set_Char_Size(fsi->face, 0, fi->real_size, msg->dpi, msg->dpi);
if (error)
error = FT_Set_Pixel_Sizes(fsi->face, 0, fi->real_size);
if (error)
{
int i, maxd = 0x7fffffff;
int chosen_size = 0;
int chosen_size2 = 0;
for (i = 0; i < fsi->face->num_fixed_sizes; i++)
{
int s, cd;
s = fsi->face->available_sizes[i].size;
cd = chosen_size - fi->real_size;
if (cd < 0) cd = -cd;
if (cd < maxd)
{
maxd = cd;
chosen_size = s;
chosen_size2 = fsi->face->available_sizes[i].y_ppem;
if (maxd == 0) break;
}
}
fi->real_size = chosen_size;
error = FT_Set_Pixel_Sizes(fsi->face, 0, fi->real_size);
if (error)
{
error = FT_Set_Char_Size(fsi->face, 0, fi->real_size, fi->dpi, fi->dpi);
if (error)
{
/* hack around broken fonts */
fi->real_size = (chosen_size2 / 64) * 60;
error = FT_Set_Char_Size(fsi->face, 0, fi->real_size, fi->dpi, fi->dpi);
if (error)
{
ERR("Could not choose the font size for font: '%s:%s'.",
msg->file, msg->name);
FT_Done_Size(fi->size);
free(fi);
return NULL;
}
}
}
}
fi->max_h = 0;
val = (int)fsi->face->bbox.yMax;
if (fsi->face->units_per_EM != 0)
{
dv = (fsi->orig_upem * 2048) / fsi->face->units_per_EM;
ret = (val * fsi->face->size->metrics.y_scale) / (dv * dv);
}
else ret = val;
fi->max_h += ret;
val = -(int)fsi->face->bbox.yMin;
if (fsi->face->units_per_EM != 0)
{
dv = (fsi->orig_upem * 2048) / fsi->face->units_per_EM;
ret = (val * fsi->face->size->metrics.y_scale) / (dv * dv);
}
else ret = val;
fi->max_h += ret;
fi->runtime_rend = FONT_REND_REGULAR;
if ((msg->rend_flags & FONT_REND_SLANT) &&
!(fsi->face->style_flags & FT_STYLE_FLAG_ITALIC))
fi->runtime_rend |= FONT_REND_SLANT;
if ((msg->rend_flags & FONT_REND_WEIGHT) &&
!(fsi->face->style_flags & FT_STYLE_FLAG_BOLD))
fi->runtime_rend |= FONT_REND_WEIGHT;
return fi;
}
static Slave_Msg_Font_Loaded *
_font_slave_load(const void *cmddata, void *data EINA_UNUSED)
{
const Slave_Msg_Font_Load *msg = cmddata;
Slave_Msg_Font_Loaded *response;
Font_Source_Info *fsi;
Font_Info *fi;
DBG("slave received FONT_LOAD: '%s'", msg->name);
fsi = msg->ftdata1;
/* Loading Font Source */
if (!fsi)
fsi = _font_slave_source_load(msg->file, msg->name);
// FIXME: Return correct error message
if (!fsi)
{
ERR("Could not load font source: '%s'", msg->file);
return NULL;
}
fi = _font_slave_int_load(msg, fsi);
if (!fi)
{
if (!msg->ftdata1)
cserve2_font_source_ft_free(fsi);
return NULL;
}
response = calloc(1, sizeof(*response));
response->ftdata1 = fsi;
response->ftdata2 = fi;
return response;
}
static Shm_Handle *
_font_slave_memory_alloc(Font_Info *fi)
{
Shm_Handle *shm = cserve2_shm_request(fi->shmsize);
return shm;
}
/* This function will load the "index" glyph to the glyph slot of the font.
* In order to use or render it, one should access it from the glyph slot,
* or get the glyph using FT_Get_Glyph().
*/
static Eina_Bool
_font_slave_glyph_load(Font_Info *fi, unsigned int idx, unsigned int hint)
{
Font_Source_Info *fsi = fi->fsi;
FT_Error error;
const FT_Int32 hintflags[3] =
{ FT_LOAD_NO_HINTING, FT_LOAD_FORCE_AUTOHINT, FT_LOAD_NO_AUTOHINT };
static FT_Matrix transform = {0x10000, _EVAS_FONT_SLANT_TAN * 0x10000,
0x00000, 0x10000};
error = FT_Load_Glyph(fsi->face, idx,
FT_LOAD_DEFAULT | FT_LOAD_NO_BITMAP |
hintflags[hint]);
if (error)
{
ERR("Could not load glyph %d", idx);
return EINA_FALSE;
}
/* Transform the outline of Glyph according to runtime_rend. */
if (fi->runtime_rend & FONT_REND_SLANT)
FT_Outline_Transform(&fsi->face->glyph->outline, &transform);
/* Embolden the outline of Glyph according to rundtime_rend. */
if (fi->runtime_rend & FONT_REND_WEIGHT)
FT_GlyphSlot_Embolden(fsi->face->glyph);
return EINA_TRUE;
}
/* This function will render the glyph currently in the glyph slot into the
* given Font Cache.
*/
static Eina_Bool
_font_slave_glyph_render(Font_Info *fi, Slave_Msg_Font_Cache *c, unsigned int idx)
{
Font_Source_Info *fsi = fi->fsi;
unsigned int glyphsize;
char *cachedata = cserve2_shm_map(c->shm);
FT_Glyph glyph;
FT_BitmapGlyph bglyph;
FT_Get_Glyph(fsi->face->glyph, &glyph);
FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
bglyph = (FT_BitmapGlyph)glyph;
glyphsize = bglyph->bitmap.pitch * bglyph->bitmap.rows;
if (c->usage + glyphsize > cserve2_shm_size_get(c->shm))
{
FT_Done_Glyph(glyph);
return EINA_FALSE;
}
memcpy(cachedata + c->usage, bglyph->bitmap.buffer, glyphsize);
// TODO: Check if we have problems with alignment
c->glyphs[c->nglyphs].index = idx;
c->glyphs[c->nglyphs].offset = c->usage;
c->glyphs[c->nglyphs].size = glyphsize;
c->glyphs[c->nglyphs].rows = bglyph->bitmap.rows;
c->glyphs[c->nglyphs].width = bglyph->bitmap.width;
c->glyphs[c->nglyphs].pitch = bglyph->bitmap.pitch;
c->glyphs[c->nglyphs].num_grays = bglyph->bitmap.num_grays;
c->glyphs[c->nglyphs].pixel_mode = bglyph->bitmap.pixel_mode;
c->usage += glyphsize;
c->nglyphs++;
FT_Done_Glyph(glyph);
return EINA_TRUE;
}
static void
_font_slave_int_metrics_get(Font_Info *fi, unsigned int hint, unsigned int c, int *width, int *height, int *depth)
{
unsigned int idx;
Font_Source_Info *fsi = fi->fsi;
FT_BBox outbox;
FT_Glyph glyph;
idx = FT_Get_Char_Index(fsi->face, c);
if (!idx)
goto end;
if (!_font_slave_glyph_load(fi, idx, hint))
goto end;
FT_Get_Glyph(fsi->face->glyph, &glyph);
FT_Glyph_Get_CBox(glyph,
((hint == 0) ? FT_GLYPH_BBOX_UNSCALED :
FT_GLYPH_BBOX_GRIDFIT),
&outbox);
if (width) *width = EVAS_FONT_ROUND_26_6_TO_INT(outbox.xMax - outbox.xMin);
if (height)
*height = EVAS_FONT_ROUND_26_6_TO_INT(outbox.yMax - outbox.xMin);
if (depth) *depth = 1; // FIXME: Do we need to check this?
FT_Done_Glyph(glyph);
return;
end:
if (width) *width = 0;
if (height) *height = 0;
if (depth) *depth = 0;
}
static unsigned int
_font_slave_int_shm_prev_calculate(unsigned int size, unsigned int nglyphs)
{
unsigned int average = size / nglyphs;
unsigned int newsize;
newsize = MIN_GLYPHS * average;
newsize = cserve2_shm_size_normalize(newsize);
if (newsize > MAX_CACHE_SIZE)
return MAX_CACHE_SIZE;
return newsize;
}
static unsigned int
_font_slave_int_shm_calculate(Font_Info *fi, unsigned int hint)
{
const char *c;
int i;
int size = 0;
int average;
for (c = CHECK_CHARS, i = 0; *c != '\0'; c++, i++)
{
int w, h, depth;
_font_slave_int_metrics_get(fi, hint, *c, &w, &h, &depth);
size += w * h * depth;
}
average = size / i; // average glyph size
size = MIN_GLYPHS * average;
size = cserve2_shm_size_normalize(size);
if (size > MAX_CACHE_SIZE)
return MAX_CACHE_SIZE; // Assumes no glyph will be bigger than this
return size;
}
#ifdef DEBUG_LOAD_TIME
static int
_timeval_sub(const struct timeval *tv2, const struct timeval *tv1)
{
int t1, t2;
t1 = tv1->tv_usec + tv1->tv_sec * 1000000;
t2 = tv2->tv_usec + tv2->tv_sec * 1000000;
if (t2 > t1)
return t2 - t1;
return 0;
}
#endif
static Slave_Msg_Font_Glyphs_Loaded *
_font_slave_glyphs_load(const void *cmddata, void *data EINA_UNUSED)
{
const Slave_Msg_Font_Glyphs_Load *msg = cmddata;
Slave_Msg_Font_Glyphs_Loaded *response;
Font_Info *fi;
unsigned int i;
unsigned int total_glyphs = 0;
#ifdef DEBUG_LOAD_TIME
unsigned int gl_load_time = 0;
unsigned int gl_render_time = 0;
struct timeval tv_start, tv_end;
struct timeval rstart, rfinish;
#endif
Eina_List *caches = NULL;
Slave_Msg_Font_Cache *c = NULL;
fi = msg->font.ftdata2;
#ifdef DEBUG_LOAD_TIME
gettimeofday(&rstart, NULL);
#endif
_font_slave_size_use(fi);
if (msg->cache.shm)
{
c = malloc(sizeof(*c) + sizeof(Slave_Msg_Glyph) *
(msg->glyphs.nglyphs));
c->nglyphs = 0;
c->glyphs = (void *)(c + 1);
c->shm = msg->cache.shm;
c->usage = msg->cache.usage;
caches = eina_list_append(caches, c);
total_glyphs = msg->cache.nglyphs;
}
if (!fi->shmsize)
fi->shmsize = _font_slave_int_shm_calculate(fi, msg->font.hint);
i = 0;
while (i < msg->glyphs.nglyphs)
{
Eina_Bool r = EINA_TRUE;
if (!c)
{
Shm_Handle *shm;
shm = _font_slave_memory_alloc(fi);
c = malloc(sizeof(*c) + sizeof(Slave_Msg_Glyph) *
(msg->glyphs.nglyphs - i));
c->nglyphs = 0;
c->glyphs = (void *)(c + 1);
c->shm = shm;
c->usage = 0;
caches = eina_list_append(caches, c);
total_glyphs = 0;
}
#ifdef DEBUG_LOAD_TIME
gettimeofday(&tv_start, NULL);
#endif
if (_font_slave_glyph_load(fi, msg->glyphs.glyphs[i], msg->font.hint))
{
#ifdef DEBUG_LOAD_TIME
gettimeofday(&tv_end, NULL);
gl_load_time += _timeval_sub(&tv_end, &tv_start);
// copy the time that we got here to be used as start of render
tv_start.tv_sec = tv_end.tv_sec;
tv_start.tv_usec = tv_end.tv_usec;
#endif
r = _font_slave_glyph_render(fi, c, msg->glyphs.glyphs[i]);
#ifdef DEBUG_LOAD_TIME
gettimeofday(&tv_end, NULL);
gl_render_time += _timeval_sub(&tv_end, &tv_start);
#endif
}
if (!r) // SHM is full
{
fi->shmsize = _font_slave_int_shm_prev_calculate
(c->usage, total_glyphs);
c = NULL;
continue;
}
i++;
total_glyphs++;
}
response = malloc(sizeof(*response) +
sizeof(c) * eina_list_count(caches));
response->ncaches = eina_list_count(caches);
response->caches = (void *)(response + 1);
i = 0;
EINA_LIST_FREE(caches, c)
response->caches[i++] = c;
#ifdef DEBUG_LOAD_TIME
response->gl_load_time = gl_load_time;
response->gl_render_time = gl_render_time;
gettimeofday(&rfinish, NULL);
response->gl_slave_time = _timeval_sub(&rfinish, &rstart);
#endif
return response;
}
void *
cserve2_font_slave_cb(Slave_Thread_Data *sd EINA_UNUSED, Slave_Command *cmd, const void *cmddata, void *data)
{
void *response = NULL;
switch (*cmd)
{
case FONT_LOAD:
response = _font_slave_load(cmddata, data);
break;
case FONT_GLYPHS_LOAD:
response = _font_slave_glyphs_load(cmddata, data);
break;
default:
ERR("Invalid command for font slave: %d", *cmd);
*cmd = ERROR;
return _font_slave_error_send(CSERVE2_INVALID_COMMAND);
}
if (!response)
{
*cmd = ERROR;
return _font_slave_error_send(CSERVE2_GENERIC);
}
return response;
}
void
cserve2_font_init(void)
{
int error;
if (initialised++ > 0) return;
error = FT_Init_FreeType(&cserve2_ft_lib);
if (error) return;
eet_init();
}
void
cserve2_font_shutdown(void)
{
initialised--;
if (initialised > 0) return;
if (initialised < 0)
{
ERR("Invalid shutdown of cserve2 font.");
return;
}
FT_Done_FreeType(cserve2_ft_lib);
cserve2_ft_lib = 0;
eet_shutdown();
}
void
cserve2_font_source_ft_free(void *fontsource)
{
Font_Source_Info *fsi = fontsource;
FT_Done_Face(fsi->face);
free(fsi->data);
free(fsi);
}
void
cserve2_font_ft_free(void *fontinfo)
{
Font_Info *fi = fontinfo;
FT_Done_Size(fi->size);
free(fi);
}