legacy-imlib2/font.c

815 lines
18 KiB
C

#include "common.h"
#include <X11/Xlib.h>
#include "colormod.h"
#include "image.h"
#include "blend.h"
#include <freetype.h>
#include "font.h"
#include <sys/types.h>
#include <string.h>
#include "file.h"
#include "updates.h"
#include "rgbadraw.h"
#define TT_VALID( handle ) ( ( handle ).z != NULL )
/* cached font list and font path */
static ImlibFont *fonts = NULL;
static int fpath_num = 0;
static char **fpath = NULL;
static TT_Engine engine;
static char have_engine = 0;
static int font_cache_size = 0;
/* lookupt table of raster_map -> RGBA Alpha values */
static int rend_lut[9] =
{ 0, 64, 128, 192, 256, 256, 256, 256, 256};
/* create an rmap of width and height */
TT_Raster_Map *
__imlib_create_font_raster(int width, int height)
{
TT_Raster_Map *rmap;
rmap = malloc(sizeof(TT_Raster_Map));
rmap->width = (width + 3) & -4;
rmap->rows = height;
rmap->flow = TT_Flow_Up;
rmap->cols = rmap->width;
rmap->size = rmap->rows * rmap->width;
rmap->bitmap = malloc(rmap->size);
memset(rmap->bitmap, 0, rmap->size);
return rmap;
}
/* free the rmap */
void
__imlib_destroy_font_raster(TT_Raster_Map * rmap)
{
free(rmap->bitmap);
free(rmap);
}
void
__imlib_add_font_path(char *path)
{
fpath_num++;
if (fpath_num == 1)
fpath = malloc(sizeof(char *));
else
fpath = realloc(fpath, (fpath_num * sizeof(char *)));
fpath[fpath_num - 1] = strdup(path);
}
void
__imlib_del_font_path(char *path)
{
int i, j;
for (i = 0; i < fpath_num; i++)
{
if (!strcmp(path, fpath[i]))
{
fpath_num--;
for (j = i; j < fpath_num; j++)
fpath[j] = fpath[j + 1];
if (fpath_num > 0)
fpath = realloc(fpath, fpath_num * sizeof(char *));
else
{
free(fpath);
fpath = NULL;
}
}
}
}
char **
__imlib_list_font_path(int *num_ret)
{
*num_ret = fpath_num;
return fpath;
}
ImlibFont *
__imlib_find_cached_font(char *fontname)
{
ImlibFont *pf, *f;
pf = NULL;
f = fonts;
while(f)
{
if (!strcmp(f->name, fontname))
{
/* if it's not the top of the list - move it there */
if (pf)
{
pf->next = f->next;
f->next = fonts;
fonts = f;
}
return f;
}
pf = f;
f = f->next;
}
return NULL;
}
ImlibFont *
__imlib_load_font(char *fontname)
{
ImlibFont *f;
TT_Error error;
TT_CharMap char_map;
TT_Glyph_Metrics metrics;
TT_Instance_Metrics imetrics;
int dpi = 96;
unsigned short i, n, code, load_flags;
unsigned short num_glyphs = 0, no_cmap = 0;
unsigned short platform, encoding;
int size, j, upm, ascent, descent;
char *name, *file = NULL, *tmp;
/* find a cached font */
f = __imlib_find_cached_font(fontname);
if (f)
{
/* reference it up by one and return it */
f->references++;
return f;
}
/* split fontname into file and size */
/* if we dont have a truetype font engine yet - make one */
if (!have_engine)
{
error = TT_Init_FreeType(&engine);
if (error)
return NULL;
have_engine = 1;
}
/* split font name (in format name/size) */
for (j = strlen(fontname) - 1;
(j >= 0) && (fontname[j] != '/');
j--);
/* no "/" in font after the first char */
if (j <= 0)
return NULL;
/* get size */
size = atoi(&(fontname[j + 1]));
/* split name in front off */
name = malloc(j * sizeof(char));
memcpy(name, fontname, j);
name[j] = 0;
/* find file if it exists */
for (j = 0; (j < fpath_num) && (!file); j++)
{
tmp = malloc(strlen(fpath[j]) + 1 + strlen(name) + 4 + 1);
if (!tmp)
{
free(name);
return NULL;
}
sprintf(tmp, "%s/%s.ttf", fpath[j], name);
if (__imlib_FileIsFile(tmp))
file = strdup(tmp);
else
{
sprintf(tmp, "%s/%s.TTF", fpath[j], name);
if (__imlib_FileIsFile(tmp))
file = strdup(tmp);
else
{
sprintf(tmp, "%s/%s", fpath[j], name);
if (__imlib_FileIsFile(tmp))
file = strdup(tmp);
}
}
free(tmp);
}
free(name);
/* didnt find a file? abort */
if (!file)
return NULL;
/* allocate */
f = malloc(sizeof(ImlibFont));
/* put in name and references */
f->name = strdup(fontname);
f->references = 1;
/* remember engine */
f->engine = engine;
f->mem_use = 0;
error = TT_Open_Face(f->engine, file, &f->face);
if (error)
{
free(f->name);
free(f);
/* fprintf(stderr, "Unable to open font\n"); */
return NULL;
}
free(file);
error = TT_Get_Face_Properties(f->face, &f->properties);
if (error)
{
TT_Close_Face(f->face);
free(f->name);
free(f);
/* fprintf(stderr, "Unable to get face properties\n"); */
return NULL;
}
error = TT_New_Instance(f->face, &f->instance);
if (error)
{
TT_Close_Face(f->face);
free(f->name);
free(f);
/* fprintf(stderr, "Unable to create instance\n"); */
return NULL;
}
TT_Set_Instance_Resolutions(f->instance, dpi, dpi);
TT_Set_Instance_CharSize(f->instance, size * 64);
n = f->properties.num_CharMaps;
/* get ascent & descent */
TT_Get_Instance_Metrics(f->instance, &imetrics);
upm = f->properties.header->Units_Per_EM;
ascent = (f->properties.horizontal->Ascender * imetrics.y_ppem) / upm;
descent = (f->properties.horizontal->Descender * imetrics.y_ppem) / upm;
if (descent < 0)
descent = -descent;
f->ascent = ascent;
f->descent = descent;
for (i = 0; i < n; i++)
{
TT_Get_CharMap_ID(f->face, i, &platform, &encoding);
if ((platform == 3 && encoding == 1) ||
(platform == 0 && encoding == 0))
{
TT_Get_CharMap(f->face, i, &char_map);
break;
}
}
if (i == n)
{
no_cmap = 1;
num_glyphs = f->properties.num_Glyphs;
TT_Done_Instance(f->instance);
TT_Close_Face(f->face);
free(f->name);
free(f);
return NULL;
}
f->num_glyph = 256;
f->glyphs = (TT_Glyph *)malloc(f->num_glyph * sizeof(TT_Glyph));
memset(f->glyphs, 0, f->num_glyph * sizeof(TT_Glyph));
f->glyphs_cached_right =
(TT_Raster_Map **)malloc(f->num_glyph * sizeof(TT_Raster_Map *));
memset(f->glyphs_cached_right, 0, f->num_glyph * sizeof(TT_Raster_Map *));
load_flags = TTLOAD_SCALE_GLYPH | TTLOAD_HINT_GLYPH;
f->max_descent = 0;
f->max_ascent = 0;
for (i = 0; i < f->num_glyph; ++i)
{
if (TT_VALID(f->glyphs[i]))
continue;
if (no_cmap)
{
code = (i - ' ' + 1) < 0 ? 0 : (i - ' ' + 1);
if (code >= num_glyphs)
code = 0;
}
else
code = TT_Char_Index(char_map, i);
TT_New_Glyph(f->face, &f->glyphs[i]);
TT_Load_Glyph(f->instance, f->glyphs[i], code, load_flags);
TT_Get_Glyph_Metrics(f->glyphs[i], &metrics);
if ((metrics.bbox.yMin & -64) < f->max_descent)
f->max_descent = (metrics.bbox.yMin & -64);
if (((metrics.bbox.yMax + 63) & -64) > f->max_ascent)
f->max_ascent = ((metrics.bbox.yMax + 63) & -64);
}
/* work around broken fonts - some just have wrogn ascent and */
/* descent members */
if (((f->ascent == 0) && (f->descent == 0)) || (f->ascent == 0))
{
f->ascent = f->max_ascent / 64;
f->descent = -f->max_descent / 64;
}
/* all ent well in loading, so add to head of font list and return */
f->next = fonts;
fonts = f;
/* we dont need the file handle hanging around so flush it out */
TT_Flush_Face(f->face);
return f;
}
void
__imlib_calc_size(ImlibFont *f, int *width, int *height, char *text)
{
int i, ascent, descent, pw, ph;
TT_Glyph_Metrics gmetrics;
ascent = f->ascent;
descent = f->descent;
pw = 0;
ph = ((f->max_ascent) - f->max_descent) / 64;
for (i = 0; text[i]; i++)
{
unsigned char j;
j = text[i];
if (!TT_VALID(f->glyphs[j]))
continue;
TT_Get_Glyph_Metrics(f->glyphs[j], &gmetrics);
if (i == 0)
pw += ((-gmetrics.bearingX) / 64);
if (text[i + 1] == 0)
pw += (gmetrics.bbox.xMax / 64);
else
pw += gmetrics.advance / 64;
}
*width = pw;
*height = ph;
}
void
__imlib_render_str(ImlibImage *im, ImlibFont *fn, int drx, int dry, char *text,
DATA8 r, DATA8 g, DATA8 b, DATA8 a,
char dir, int *retw, int *reth, int blur,
int *nextx, int *nexty, ImlibOp op)
{
DATA32 lut[9], *p, *tmp;
TT_Glyph_Metrics metrics;
TT_F26Dot6 x, y, xmin, ymin, xmax, ymax;
int w, h, i, ioff, iread, xor, yor;
char *off, *read, *_off, *_read;
int x_offset, y_offset;
unsigned char j;
TT_Raster_Map *rtmp = NULL, *rmap;
ImlibImage im2;
/* if we draw outside the image from here - give up */
if ((drx > im->w) || (dry > im->h))
{
if ((retw) || (reth))
{
__imlib_calc_size(fn, &w, &h, text);
if (retw)
*retw = w;
if (reth)
*reth = h;
}
return;
}
/* build LUT table */
for (i = 0; i < 9; i++)
lut[i] = (DATA32)(
((((rend_lut[i] * (int)a) >> 8) & 0xff) << 24) |
((int)r << 16) |
((int)g << 8) |
((int)b));
/* get offset of first char */
j = text[0];
TT_Get_Glyph_Metrics(fn->glyphs[j], &metrics);
x_offset = (-metrics.bearingX) / 64;
y_offset = -(fn->max_descent / 64);
xor = x_offset;
yor = rmap->rows - y_offset;
/* figure out the size this text string is going to be */
__imlib_calc_size(fn, &w, &h, text);
switch(dir)
{
case 0:
case 1:
if (retw)
*retw = w;
if (reth)
*reth = h;
if (*nexty)
*nexty = fn->ascent + fn->descent;
if (*nextx)
{
j = text[strlen(text) - 1];
TT_Get_Glyph_Metrics(fn->glyphs[j], &metrics);
*nextx = w - x_offset + (metrics.advance / 64) -
(metrics.bbox.xMax / 64);
}
break;
case 2:
case 3:
if (retw)
*retw = h;
if (reth)
*reth = w;
if (*nextx)
*nextx = fn->ascent + fn->descent;
if (*nexty)
{
j = text[strlen(text) - 1];
TT_Get_Glyph_Metrics(fn->glyphs[j], &metrics);
*nexty = w - x_offset + (metrics.advance / 64) -
(metrics.bbox.xMax / 64);
}
break;
default:
break;
}
/* if the text is completely outside the image - give up */
if (((drx + w) <= 0) || ((dry + h) <= 0))
return;
/* create a scratch pad for it */
rmap = __imlib_create_font_raster(w, h);
rmap->flow = TT_Flow_Up;
/* render the text into the scratch pad */
for (i = 0; text[i]; i++)
{
j = text[i];
if (!TT_VALID(fn->glyphs[j]))
continue;
TT_Get_Glyph_Metrics(fn->glyphs[j], &metrics);
xmin = metrics.bbox.xMin & -64;
ymin = metrics.bbox.yMin & -64;
xmax = (metrics.bbox.xMax + 63) & -64;
ymax = (metrics.bbox.yMax + 63) & -64;
if (fn->glyphs_cached_right[j])
rtmp = fn->glyphs_cached_right[j];
else
{
rtmp = __imlib_create_font_raster(((xmax - xmin) / 64) + 1,
((ymax - ymin) / 64) + 1);
TT_Get_Glyph_Pixmap(fn->glyphs[j], rtmp, -xmin, -ymin);
fn->glyphs_cached_right[j] = rtmp;
fn->mem_use +=
(((xmax - xmin) / 64) + 1) *
(((ymax - ymin) / 64) + 1);
}
/* Blit-or the resulting small pixmap into the biggest one */
/* We do that by hand, and provide also clipping. */
xmin = (xmin >> 6) + x_offset;
ymin = (ymin >> 6) + y_offset;
xmax = (xmax >> 6) + x_offset;
ymax = (ymax >> 6) + y_offset;
/* Take care of comparing xmin and ymin with signed values! */
/* This was the cause of strange misplacements when Bit.rows */
/* was unsigned. */
if ((xmin >= (int)rmap->width) || (ymin >= (int)rmap->rows) ||
(xmax < 0) || (ymax < 0))
continue;
/* Note that the clipping check is performed _after_ rendering */
/* the glyph in the small bitmap to let this function return */
/* potential error codes for all glyphs, even hidden ones. */
/* In exotic glyphs, the bounding box may be larger than the */
/* size of the small pixmap. Take care of that here. */
if (xmax - xmin + 1 > rtmp->width)
xmax = xmin + rtmp->width - 1;
if (ymax - ymin + 1 > rtmp->rows)
ymax = ymin + rtmp->rows - 1;
/* set up clipping and cursors */
iread = 0;
if (ymin < 0)
{
iread -= ymin * rtmp->cols;
ioff = 0;
ymin = 0;
}
else
ioff = (rmap->rows - ymin - 1) * rmap->cols;
if (ymax >= rmap->rows)
ymax = rmap->rows - 1;
if (xmin < 0)
{
iread -= xmin;
xmin = 0;
}
else
ioff += xmin;
if (xmax >= rmap->width)
xmax = rmap->width - 1;
_read = (char *)rtmp->bitmap + iread;
_off = (char *)rmap->bitmap + ioff;
for (y = ymin; y <= ymax; y++)
{
read = _read;
off = _off;
for (x = xmin; x <= xmax; x++)
{
*off |= *read;
off++;
read++;
}
_read += rtmp->cols;
_off -= rmap->cols;
}
x_offset += metrics.advance / 64;
}
/* temporary RGBA buffer to build */
tmp = malloc(rmap->rows * rmap->cols * sizeof(DATA32));
p = tmp;
read = rmap->bitmap;
/* build the buffer */
for (x = 0; x < rmap->size; x++)
{
*p = lut[(int)(*read)];
p++;
read++;
}
/* blend buffer onto image */
im2.data = tmp;
im2.w = rmap->cols;
im2.h = rmap->rows;
if (blur > 0)
__imlib_BlurImage(&im2, blur);
switch(dir)
{
case 0: /* to right */
break;
case 1: /* to left */
__imlib_FlipImageHoriz(&im2);
__imlib_FlipImageVert(&im2);
break;
case 2: /* to down */
__imlib_FlipImageDiagonal(&im2);
break;
case 3: /* to up */
__imlib_FlipImageDiagonal(&im2);
__imlib_FlipImageHoriz(&im2);
__imlib_FlipImageVert(&im2);
break;
default:
break;
}
tmp = im2.data;
if (IMAGE_HAS_ALPHA(im))
__imlib_BlendRGBAToData(tmp, im2.w, im2.h,
im->data, im->w, im->h,
0, 0, drx, dry, im2.w, im2.h,
1, NULL, op);
else
__imlib_BlendRGBAToData(tmp, im2.w, im2.h,
im->data, im->w, im->h,
0, 0, drx, dry, im2.w, im2.h,
0, NULL, op);
free(tmp);
__imlib_destroy_font_raster(rmap);
}
int
__imlib_char_pos(ImlibFont *fn, char *text, int x, int y,
int *cx, int *cy, int *cw, int *ch)
{
int i, px, ppx;
TT_Glyph_Metrics gmetrics;
if ((y < 0) || (y > (fn->ascent + fn->descent)))
return -1;
if (cy)
*cy = 0;
if (ch)
*ch = fn->ascent + fn->descent;
ppx = 0;
px = 0;
for (i = 0; text[i]; i++)
{
unsigned char j;
j = text[i];
if (!TT_VALID(fn->glyphs[j]))
continue;
TT_Get_Glyph_Metrics(fn->glyphs[j], &gmetrics);
ppx = px;
if (i == 0)
px += ((-gmetrics.bearingX) / 64);
if (text[i + 1] == 0)
px += (gmetrics.bbox.xMax / 64);
else
px += gmetrics.advance / 64;
if ((x >= ppx) && (x < px))
{
if (cx)
*cx = ppx;
if (cw)
*cw = px - ppx;
return i;
}
}
return -1;
}
char **
__imlib_list_fonts(int *num_ret)
{
int i, j, d, l = 0;
char **list = NULL, **dir, *path;
TT_Error error;
/* if we dont have a truetype font engine yet - make one */
if (!have_engine)
{
error = TT_Init_FreeType(&engine);
if (error)
return NULL;
have_engine = 1;
}
for (i = 0; i < fpath_num; i++)
{
dir = __imlib_FileDir(fpath[i], &d);
if (dir)
{
for (j = 0; j < d; j++)
{
if (__imlib_FileIsFile(dir[j]))
{
TT_Face f;
path = malloc(strlen(fpath[i]) + 1 + strlen(dir[j] + 1));
strcpy(path, fpath[i]);
strcat(path, "/");
strcat(path, dir[j]);
error = TT_Open_Face(engine, path, &f);
free(path);
if (!error)
{
TT_Close_Face(f);
l++;
if (list)
list = realloc(list, sizeof(char *) * l);
else
list = malloc(sizeof(char *));
list[l - 1] = strdup(dir[j]);
}
}
}
free(dir);
}
}
*num_ret = l;
return list;
}
void
__imlib_free_font_list(char **list, int num)
{
__imlib_FileFreeDirList(list, num);
}
int
__imlib_get_cached_font_size(void)
{
ImlibFont *f;
int num = 0;
f = fonts;
while(f)
{
if (f->references == 0)
num += f->mem_use;
f = f->next;
}
return num;
}
void
__imlib_flush_font_cache(void)
{
int size;
ImlibFont *flast, *f;
size = __imlib_get_cached_font_size();
while (size > font_cache_size)
{
flast = NULL;
f = fonts;
while (f)
{
if (f->references == 0)
flast = f;
f = f->next;
}
if (flast)
{
size -= flast->mem_use;
__imlib_nuke_font(flast);
}
}
}
void
__imlib_purge_font_cache(void)
{
ImlibFont *pf, *f;
f = fonts;
while(f)
{
pf = f;
f = f->next;
if (pf->references == 0)
__imlib_nuke_font(pf);
}
if (!fonts)
{
if (have_engine)
{
TT_Done_FreeType(engine);
have_engine = 0;
}
}
}
int
__imlib_get_font_cache_size(void)
{
return font_cache_size;
}
void
__imlib_set_font_cache_size(int size)
{
if (size < 0)
size = 0;
font_cache_size = size;
__imlib_flush_font_cache();
}
void
__imlib_free_font(ImlibFont *font)
{
/* defererence */
font->references--;
/* if still referenced exit here */
if (font->references > 0)
return;
__imlib_flush_font_cache();
}
void
__imlib_nuke_font(ImlibFont *font)
{
int i;
ImlibFont *f, *pf;
/* remove form font cache list */
pf = NULL;
f = fonts;
while (f)
{
if (f == font)
{
if (!pf)
fonts = f->next;
else
pf->next = f->next;
f = NULL;
}
else
{
pf = f;
f = f->next;
}
}
/* free freetype instance stuff */
TT_Done_Instance(font->instance);
TT_Close_Face(font->face);
/* free all cached glyphs */
for (i = 0; i < font->num_glyph; i++)
{
if ((font->glyphs_cached_right) && (font->glyphs_cached_right[i]))
__imlib_destroy_font_raster(font->glyphs_cached_right[i]);
if (!TT_VALID(font->glyphs[i]))
TT_Done_Glyph(font->glyphs[i]);
}
/* free glyph info */
free(font->glyphs);
/* free glyph cache arrays */
if (font->glyphs_cached_right)
free(font->glyphs_cached_right);
/* free font struct & name */
free(font->name);
free(font);
}