popup: replace FreeType renderer with PangoCairo
This commit is contained in:
254
font.c
254
font.c
@@ -1,168 +1,166 @@
|
||||
#include "dat.h"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <pango/pangocairo.h>
|
||||
#include "fn.h"
|
||||
|
||||
typedef struct Font Font;
|
||||
struct Font
|
||||
static PangoFontMap *fontmap;
|
||||
static PangoContext *context;
|
||||
static PangoLayout *layout;
|
||||
|
||||
static void
|
||||
textclear(void)
|
||||
{
|
||||
FT_Face face;
|
||||
int base;
|
||||
};
|
||||
|
||||
static FT_Library lib;
|
||||
static Font fonts[Maxfonts];
|
||||
static int nfonts;
|
||||
static u32int blendtab[2][256];
|
||||
|
||||
static u32int
|
||||
blend(u32int bg, u32int fg, int a)
|
||||
{
|
||||
int r, g, b, inv;
|
||||
|
||||
inv = 255 - a;
|
||||
r = ((bg >> 16 & 0xff) * inv + (fg >> 16 & 0xff) * a) / 255;
|
||||
g = ((bg >> 8 & 0xff) * inv + (fg >> 8 & 0xff) * a) / 255;
|
||||
b = ((bg & 0xff) * inv + (fg & 0xff) * a) / 255;
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static int
|
||||
loadfont(char *path)
|
||||
{
|
||||
FT_Face face;
|
||||
Font *f;
|
||||
vlong height, pixels;
|
||||
|
||||
if(nfonts >= nelem(fonts))
|
||||
return 0;
|
||||
if(FT_New_Face(lib, path, 0, &face) != 0){
|
||||
fprint(2, "strans: popup: can't load font: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
height = (vlong)face->ascender - face->descender;
|
||||
if(height <= 0 || face->units_per_EM == 0){
|
||||
fprint(2, "strans: popup: invalid font metrics: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
pixels = (vlong)Fontsz * face->units_per_EM / height;
|
||||
if(pixels < 1)
|
||||
pixels = 1;
|
||||
if(FT_Set_Pixel_Sizes(face, 0, pixels) != 0){
|
||||
fprint(2, "strans: popup: can't size font: %s\n", path);
|
||||
FT_Done_Face(face);
|
||||
return 0;
|
||||
}
|
||||
f = &fonts[nfonts];
|
||||
f->face = face;
|
||||
f->base = (vlong)Fontsz * face->ascender / height;
|
||||
nfonts++;
|
||||
return 1;
|
||||
if(layout != nil)
|
||||
g_object_unref(layout);
|
||||
if(context != nil)
|
||||
g_object_unref(context);
|
||||
if(fontmap != nil)
|
||||
g_object_unref(fontmap);
|
||||
layout = nil;
|
||||
context = nil;
|
||||
fontmap = nil;
|
||||
}
|
||||
|
||||
static void
|
||||
clearfonts(void)
|
||||
setfont(void)
|
||||
{
|
||||
int i;
|
||||
PangoFontDescription *font;
|
||||
PangoRectangle r;
|
||||
int size;
|
||||
|
||||
for(i = 0; i < nfonts; i++){
|
||||
FT_Done_Face(fonts[i].face);
|
||||
fonts[i].face = nil;
|
||||
font = pango_font_description_new();
|
||||
pango_font_description_set_family(font, "sans");
|
||||
pango_font_description_set_absolute_size(font, Fontsz * PANGO_SCALE);
|
||||
pango_layout_set_font_description(layout, font);
|
||||
pango_layout_set_text(layout, "Mg", -1);
|
||||
pango_layout_get_pixel_extents(layout, nil, &r);
|
||||
if(r.height > 0){
|
||||
/* Fontsz is the popup row height, not a point size. */
|
||||
size = Fontsz * PANGO_SCALE * Fontsz / r.height;
|
||||
pango_font_description_set_absolute_size(font, max(size, PANGO_SCALE));
|
||||
pango_layout_set_font_description(layout, font);
|
||||
}
|
||||
nfonts = 0;
|
||||
pango_font_description_free(font);
|
||||
}
|
||||
|
||||
int
|
||||
fontinit(char **path, int npath)
|
||||
textinit(char **path, int npath)
|
||||
{
|
||||
int i, a;
|
||||
GError *err;
|
||||
int i;
|
||||
|
||||
if(path == nil || npath < 1 || npath > nelem(fonts)){
|
||||
fprint(2, "strans: popup: need 1-%d font files\n", nelem(fonts));
|
||||
clearfonts();
|
||||
textclear();
|
||||
if(path == nil || npath < 1 || npath > Maxfonts){
|
||||
fprint(2, "strans: popup: need 1-%d font files\n", Maxfonts);
|
||||
return 0;
|
||||
}
|
||||
if(lib == nil && FT_Init_FreeType(&lib) != 0){
|
||||
fprint(2, "strans: popup: can't initialize FreeType\n");
|
||||
fontmap = pango_cairo_font_map_new();
|
||||
if(fontmap == nil){
|
||||
fprint(2, "strans: popup: can't initialize PangoCairo\n");
|
||||
return 0;
|
||||
}
|
||||
clearfonts();
|
||||
for(i = 0; i < npath; i++){
|
||||
if(path[i] == nil || !loadfont(path[i])){
|
||||
clearfonts();
|
||||
if(path[i] == nil){
|
||||
fprint(2, "strans: popup: invalid font file\n");
|
||||
textclear();
|
||||
return 0;
|
||||
}
|
||||
err = nil;
|
||||
if(!pango_font_map_add_font_file(fontmap, path[i], &err)){
|
||||
fprint(2, "strans: popup: can't register font: %s: %s\n",
|
||||
path[i], err == nil ? "unknown error" : err->message);
|
||||
if(err != nil)
|
||||
g_error_free(err);
|
||||
textclear();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
for(a = 0; a < 256; a++){
|
||||
blendtab[0][a] = blend(Colbg, Colfg, a);
|
||||
blendtab[1][a] = blend(Colsel, Colfg, a);
|
||||
pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), 96);
|
||||
context = pango_font_map_create_context(fontmap);
|
||||
if(context != nil)
|
||||
layout = pango_layout_new(context);
|
||||
if(context == nil || layout == nil){
|
||||
fprint(2, "strans: popup: can't create text layout\n");
|
||||
textclear();
|
||||
return 0;
|
||||
}
|
||||
pango_layout_set_single_paragraph_mode(layout, TRUE);
|
||||
setfont();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
drawglyph(Font *f, u32int *buf, int w, int h, int px, int py)
|
||||
settext(Str *s)
|
||||
{
|
||||
FT_Bitmap *b;
|
||||
FT_GlyphSlot g;
|
||||
uchar *row;
|
||||
vlong pitch, x0, x1, y0, y1;
|
||||
int a, sel, x, xa, xb, y, ya, yb;
|
||||
u32int *p;
|
||||
char utf[Maxutf];
|
||||
int n;
|
||||
|
||||
g = f->face->glyph;
|
||||
b = &g->bitmap;
|
||||
if(b->pixel_mode != FT_PIXEL_MODE_GRAY || b->num_grays != 256)
|
||||
if(layout == nil || s == nil || s->n <= 0)
|
||||
return 0;
|
||||
if(b->width == 0 || b->rows == 0)
|
||||
return 1;
|
||||
pitch = b->pitch;
|
||||
if(pitch < 0)
|
||||
pitch = -pitch;
|
||||
if(b->buffer == nil || pitch < b->width)
|
||||
n = stoutf(s, utf, sizeof utf);
|
||||
pango_layout_set_text(layout, utf, n);
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
static void
|
||||
textextents(PangoRectangle *r)
|
||||
{
|
||||
PangoRectangle ink, logical;
|
||||
int x0, y0, x1, y1;
|
||||
|
||||
pango_layout_get_pixel_extents(layout, &ink, &logical);
|
||||
x0 = min(ink.x, logical.x);
|
||||
y0 = min(ink.y, logical.y);
|
||||
x1 = max(ink.x + ink.width, logical.x + logical.width);
|
||||
y1 = max(ink.y + ink.height, logical.y + logical.height);
|
||||
r->x = x0;
|
||||
r->y = y0;
|
||||
r->width = max(x1 - x0, 0);
|
||||
r->height = max(y1 - y0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
textwidth(Str *s)
|
||||
{
|
||||
PangoRectangle r;
|
||||
|
||||
if(!settext(s))
|
||||
return 0;
|
||||
x0 = (vlong)px + g->bitmap_left;
|
||||
y0 = (vlong)py + f->base - g->bitmap_top;
|
||||
x1 = x0 + b->width;
|
||||
y1 = y0 + b->rows;
|
||||
if(x1 <= 0 || y1 <= 0 || x0 >= w || y0 >= h)
|
||||
return 1;
|
||||
xa = x0 < 0 ? 0 : x0;
|
||||
xb = x1 > w ? w : x1;
|
||||
ya = y0 < 0 ? 0 : y0;
|
||||
yb = y1 > h ? h : y1;
|
||||
for(y = ya; y < yb; y++){
|
||||
if(b->pitch < 0)
|
||||
row = b->buffer + ((vlong)b->rows - 1 - (y - y0)) * pitch;
|
||||
else
|
||||
row = b->buffer + ((vlong)y - y0) * pitch;
|
||||
for(x = xa; x < xb; x++){
|
||||
a = row[(vlong)x - x0];
|
||||
if(a != 0){
|
||||
p = &buf[y * w + x];
|
||||
sel = *p == Colsel;
|
||||
*p = blendtab[sel][a];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
textextents(&r);
|
||||
return r.width;
|
||||
}
|
||||
|
||||
void
|
||||
putfont(u32int *buf, int w, int h, int px, int py, Rune r)
|
||||
textdraw(u32int *buf, int w, int h, int x, int y, Str *s)
|
||||
{
|
||||
FT_UInt g;
|
||||
int f;
|
||||
PangoRectangle r;
|
||||
cairo_surface_t *surface;
|
||||
cairo_t *cr;
|
||||
int stride;
|
||||
|
||||
if(buf == nil || w <= 0 || h <= 0)
|
||||
if(buf == nil || w <= 0 || h <= 0 || !settext(s))
|
||||
return;
|
||||
stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
|
||||
if(stride < 0 || (vlong)stride != (vlong)w * sizeof buf[0])
|
||||
return;
|
||||
surface = cairo_image_surface_create_for_data((uchar*)buf,
|
||||
CAIRO_FORMAT_RGB24, w, h, stride);
|
||||
if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS){
|
||||
cairo_surface_destroy(surface);
|
||||
return;
|
||||
for(f = 0; f < nfonts; f++){
|
||||
g = FT_Get_Char_Index(fonts[f].face, r);
|
||||
if(g == 0 || FT_Load_Glyph(fonts[f].face, g, FT_LOAD_DEFAULT) != 0 ||
|
||||
FT_Render_Glyph(fonts[f].face->glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||
continue;
|
||||
if(drawglyph(&fonts[f], buf, w, h, px, py))
|
||||
return;
|
||||
}
|
||||
cairo_surface_mark_dirty(surface);
|
||||
cr = cairo_create(surface);
|
||||
if(cairo_status(cr) == CAIRO_STATUS_SUCCESS){
|
||||
pango_cairo_update_layout(cr, layout);
|
||||
textextents(&r);
|
||||
/* A fallback face must not paint into another popup row. */
|
||||
cairo_rectangle(cr, 0, y, w, Fontsz);
|
||||
cairo_clip(cr);
|
||||
cairo_move_to(cr, x - r.x, y + (Fontsz - r.height) / 2 - r.y);
|
||||
cairo_set_source_rgb(cr, 0, 0, 0);
|
||||
pango_cairo_show_layout(cr, layout);
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_flush(surface);
|
||||
cairo_surface_destroy(surface);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user