Merge branch 'use-freetype-to-render-glyphs'

This commit is contained in:
Daniel Eklöf 2019-07-29 20:12:33 +02:00
commit d1b88f67e4
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
7 changed files with 327 additions and 140 deletions

View file

@ -1,5 +1,5 @@
pkgname=foot
pkgver=0.0.r2.g7379198
pkgver=0.0.r136.g90d357b
pkgrel=1
pkgdesc="A wayland native terminal emulator"
arch=('x86_64')

295
font.c
View file

@ -2,46 +2,57 @@
#include <stdlib.h>
#include <stdbool.h>
#include <wchar.h>
#include <assert.h>
#include <fontconfig/fontconfig.h>
#include <cairo-ft.h>
#define LOG_MODULE "font"
#include "log.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
static FT_Library ft_lib;
static void __attribute__((constructor))
init(void)
{
FcInit();
FT_Init_FreeType(&ft_lib);
}
static void __attribute__((destructor))
fini(void)
{
FcFini();
FT_Done_FreeType(ft_lib);
}
cairo_scaled_font_t *
font_from_name(const char *name)
static void
font_populate_glyph_cache(struct font *font)
{
memset(font->cache, 0, sizeof(font->cache));
for (size_t i = 0; i < 256; i++)
font_glyph_for_utf8(font, &(char){i}, &font->cache[i]);
}
bool
font_from_name(const char *name, struct font *font)
{
memset(font, 0, sizeof(*font));
FcPattern *pattern = FcNameParse((const unsigned char *)name);
if (pattern == NULL) {
LOG_ERR("%s: failed to lookup font", name);
return NULL;
return false;
}
if (!FcConfigSubstitute(NULL, pattern, FcMatchPattern)) {
LOG_ERR("%s: failed to do config substitution", name);
FcPatternDestroy(pattern);
return NULL;
return false;
}
cairo_font_options_t *options = cairo_font_options_create();
cairo_font_options_set_hint_style(options, CAIRO_HINT_STYLE_DEFAULT);
cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_DEFAULT);
cairo_font_options_set_subpixel_order(options, CAIRO_SUBPIXEL_ORDER_DEFAULT);
cairo_ft_font_options_substitute(options, pattern);
FcDefaultSubstitute(pattern);
FcResult result;
@ -50,54 +61,242 @@ font_from_name(const char *name)
if (final_pattern == NULL) {
LOG_ERR("%s: failed to match font", name);
return NULL;
return false;
}
FcBool fc_hinting, fc_antialias;
if (FcPatternGetBool(final_pattern, FC_HINTING,0, &fc_hinting) != FcResultMatch)
fc_hinting = FcTrue;
FcChar8 *face_file = NULL;
if (FcPatternGetString(final_pattern, FC_FT_FACE, 0, &face_file) != FcResultMatch) {
if (FcPatternGetString(final_pattern, FC_FILE, 0, &face_file) != FcResultMatch) {
LOG_ERR("no font file name available");
FcPatternDestroy(final_pattern);
return false;
}
}
if (FcPatternGetBool(final_pattern, FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch)
fc_antialias = FcTrue;
cairo_font_options_set_hint_style(
options, fc_hinting ? CAIRO_HINT_STYLE_DEFAULT : CAIRO_HINT_STYLE_NONE);
cairo_font_options_set_antialias(
options, fc_antialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
double dpi;
if (FcPatternGetDouble(final_pattern, FC_DPI, 0, &dpi) != FcResultMatch)
dpi = 96;
double size;
if (FcPatternGetDouble(final_pattern, FC_PIXEL_SIZE, 0, &size)) {
LOG_ERR("%s: failed to get size", name);
FcPatternDestroy(final_pattern);
return NULL;
return false;
}
cairo_font_face_t *face = cairo_ft_font_face_create_for_pattern(
final_pattern);
LOG_DBG("loading: %s", face_file);
FT_Face ft_face;
FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, 0, &ft_face);
if (ft_err != 0)
LOG_ERR("%s: failed to create FreeType face", face_file);
if ((ft_err = FT_Set_Char_Size(ft_face, size * 64, 0, 0, 0)) != 0)
LOG_WARN("failed to set character size");
FcBool fc_hinting;
if (FcPatternGetBool(final_pattern, FC_HINTING,0, &fc_hinting) != FcResultMatch)
fc_hinting = FcTrue;
FcBool fc_antialias;
if (FcPatternGetBool(final_pattern, FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch)
fc_antialias = FcTrue;
int fc_hintstyle;
if (FcPatternGetInteger(final_pattern, FC_HINT_STYLE, 0, &fc_hintstyle) != FcResultMatch)
fc_hintstyle = FC_HINT_SLIGHT;
int fc_rgba;
if (FcPatternGetInteger(final_pattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch)
fc_rgba = FC_RGBA_UNKNOWN;
int load_flags = 0;
if (!fc_antialias) {
if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL;
else
load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO;
} else {
if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL;
else if (fc_hinting && fc_hintstyle == FC_HINT_SLIGHT)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT;
else if (fc_rgba == FC_RGBA_RGB) {
LOG_WARN("unimplemented: subpixel antialiasing");
// load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD;
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL;
} else if (fc_rgba == FC_RGBA_VRGB) {
LOG_WARN("unimplemented: subpixel antialiasing");
//load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD_V;
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL;
} else
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL;
}
FcBool fc_embeddedbitmap;
if (FcPatternGetBool(final_pattern, FC_EMBEDDED_BITMAP, 0, &fc_embeddedbitmap) != FcResultMatch)
fc_embeddedbitmap = FcTrue;
if (!fc_embeddedbitmap)
load_flags |= FT_LOAD_NO_BITMAP;
int render_flags = 0;
if (!fc_antialias)
render_flags |= FT_RENDER_MODE_MONO;
else {
if (false)
;
#if 0
if (fc_rgba == FC_RGBA_RGB)
render_flags |= FT_RENDER_MODE_LCD;
else if (fc_rgba == FC_RGBA_VRGB)
render_flags |= FT_RENDER_MODE_LCD_V;
#endif
else
render_flags |= FT_RENDER_MODE_NORMAL;
}
int fc_lcdfilter;
if (FcPatternGetInteger(final_pattern, FC_LCD_FILTER, 0, &fc_lcdfilter) != FcResultMatch)
fc_lcdfilter = FC_LCD_DEFAULT;
switch (fc_lcdfilter) {
case FC_LCD_NONE: font->lcd_filter = FT_LCD_FILTER_NONE; break;
case FC_LCD_DEFAULT: font->lcd_filter = FT_LCD_FILTER_DEFAULT; break;
case FC_LCD_LIGHT: font->lcd_filter = FT_LCD_FILTER_LIGHT; break;
case FC_LCD_LEGACY: font->lcd_filter = FT_LCD_FILTER_LEGACY; break;
}
FcPatternDestroy(final_pattern);
if (cairo_font_face_status(face) != CAIRO_STATUS_SUCCESS) {
LOG_ERR("%s: failed to create cairo font face", name);
cairo_font_face_destroy(face);
return NULL;
}
cairo_matrix_t matrix, ctm;
cairo_matrix_init_identity(&ctm);
cairo_matrix_init_scale(&matrix, size, size);
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create(
face, &matrix, &ctm, options);
cairo_font_options_destroy(options);
cairo_font_face_destroy(face);
if (cairo_scaled_font_status(scaled_font) != CAIRO_STATUS_SUCCESS) {
LOG_ERR("%s: failed to create scaled font", name);
cairo_scaled_font_destroy(scaled_font);
return NULL;
}
return scaled_font;
mtx_init(&font->lock, mtx_plain);
font->face = ft_face;
font->load_flags = load_flags;
font->render_flags = render_flags;
font_populate_glyph_cache(font);
return true;
}
bool
font_glyph_for_utf8(struct font *font, const char *utf8,
struct glyph *glyph)
{
mbstate_t ps = {0};
wchar_t wc;
if (mbrtowc(&wc, utf8, 4, &ps) < 0) {
LOG_ERR("FAILED: %.4s", utf8);
return false;
}
wprintf(L"CONVERTED: %.1s\n", &wc);
mtx_lock(&font->lock);
/*
* LCD filter is per library instance. Thus we need to re-set it
* every time...
*
* Also note that many freetype builds lack this feature
* (FT_CONFIG_OPTION_SUBPIXEL_RENDERING must be defined, and isn't
* by default) */
FT_Error err = FT_Library_SetLcdFilter(ft_lib, font->lcd_filter);
if (err != 0 && err != FT_Err_Unimplemented_Feature)
goto err;
FT_UInt idx = FT_Get_Char_Index(font->face, wc);
err = FT_Load_Glyph(font->face, idx, font->load_flags);
if (err != 0) {
LOG_ERR("load failed");
goto err;
}
err = FT_Render_Glyph(font->face->glyph, font->render_flags);
if (err != 0)
goto err;
assert(font->face->glyph->format == FT_GLYPH_FORMAT_BITMAP);
FT_Bitmap *bitmap = &font->face->glyph->bitmap;
assert(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY ||
bitmap->pixel_mode == FT_PIXEL_MODE_MONO);
cairo_format_t cr_format = bitmap->pixel_mode == FT_PIXEL_MODE_GRAY
? CAIRO_FORMAT_A8 : CAIRO_FORMAT_A1;
int stride = cairo_format_stride_for_width(cr_format, bitmap->width);
assert(stride >= bitmap->pitch);
uint8_t *data = malloc(bitmap->rows * stride);
assert(bitmap->pitch >= 0);
switch (bitmap->pixel_mode) {
case FT_PIXEL_MODE_MONO:
for (size_t r = 0; r < bitmap->rows; r++) {
for (size_t c = 0; c < (bitmap->width + 7) / 8; c++) {
uint8_t v = bitmap->buffer[r * bitmap->pitch + c];
uint8_t reversed = 0;
for (size_t i = 0; i < min(8, bitmap->width - c * 8); i++)
reversed |= ((v >> (7 - i)) & 1) << i;
data[r * stride + c] = reversed;
}
}
break;
case FT_PIXEL_MODE_GRAY:
for (size_t r = 0; r < bitmap->rows; r++) {
for (size_t c = 0; c < bitmap->width; c++)
data[r * stride + c] = bitmap->buffer[r * bitmap->pitch + c];
}
break;
default:
LOG_ERR("unimplemented FreeType bitmap pixel mode: %d",
bitmap->pixel_mode);
free(data);
goto err;
}
cairo_surface_t *surf = cairo_image_surface_create_for_data(
data, cr_format, bitmap->width, bitmap->rows, stride);
if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) {
free(data);
cairo_surface_destroy(surf);
goto err;
}
*glyph = (struct glyph){
.data = data,
.surf = surf,
.left = font->face->glyph->bitmap_left,
.top = font->face->glyph->bitmap_top,
.format = cr_format,
.width = bitmap->width,
.height = bitmap->rows,
.stride = stride,
};
mtx_unlock(&font->lock);
return true;
err:
mtx_unlock(&font->lock);
return false;
}
void
font_destroy(struct font *font)
{
if (font->face != NULL)
FT_Done_Face(font->face);
for (size_t i = 0; i < 256; i++) {
if (font->cache[i].surf != NULL)
cairo_surface_destroy(font->cache[i].surf);
if (font->cache[i].data != NULL)
free(font->cache[i].data);
}
mtx_destroy(&font->lock);
}

10
font.h
View file

@ -1,5 +1,11 @@
#pragma once
#include <cairo.h>
#include <stdbool.h>
#include <threads.h>
cairo_scaled_font_t *font_from_name(const char *name);
#include "terminal.h"
bool font_from_name(const char *name, struct font *result);
bool font_glyph_for_utf8(
struct font *font, const char *utf8, struct glyph *glyph);
void font_destroy(struct font *font);

76
main.c
View file

@ -409,30 +409,29 @@ main(int argc, char *const *argv)
thrd_t keyboard_repeater_id;
thrd_create(&keyboard_repeater_id, &keyboard_repeater, &term);
term.fonts[0].font = font_from_name(conf.font);
if (term.fonts[0].font == NULL)
if (!font_from_name(conf.font, &term.fonts[0]))
goto out;
{
char fname[1024];
snprintf(fname, sizeof(fname), "%s:style=bold", conf.font);
term.fonts[1].font = font_from_name(fname);
font_from_name(fname, &term.fonts[1]);
snprintf(fname, sizeof(fname), "%s:style=italic", conf.font);
term.fonts[2].font = font_from_name(fname);
font_from_name(fname, &term.fonts[2]);
snprintf(fname, sizeof(fname), "%s:style=bold italic", conf.font);
term.fonts[3].font = font_from_name(fname);
font_from_name(fname, &term.fonts[3]);
}
/* Underline position and size */
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) {
struct font *f = &term.fonts[i];
if (f->font == NULL)
if (f->face == NULL)
continue;
FT_Face ft_face = cairo_ft_scaled_font_lock_face(f->font);
FT_Face ft_face = f->face;
double x_scale = ft_face->size->metrics.x_scale / 65526.;
double height = ft_face->size->metrics.height / 64;
@ -465,46 +464,27 @@ main(int argc, char *const *argv)
LOG_DBG("strikeout: pos=%f, thick=%f",
f->strikeout.position, f->strikeout.thickness);
cairo_ft_scaled_font_unlock_face(f->font);
}
cairo_scaled_font_extents(term.fonts[0].font, &term.fextents);
{
FT_Face ft_face = term.fonts[0].face;
int max_x_advance = ft_face->size->metrics.max_advance / 64;
int height = ft_face->size->metrics.height / 64;
int descent = ft_face->size->metrics.descender / 64;
int ascent = ft_face->size->metrics.ascender / 64;
term.fextents.height = height;
term.fextents.descent = -descent;
term.fextents.ascent = ascent;
term.fextents.max_x_advance = max_x_advance;
LOG_WARN("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d",
height, descent, ascent, max_x_advance);
}
term.cell_width = (int)ceil(term.fextents.max_x_advance);
term.cell_height = (int)ceil(term.fextents.height);
LOG_DBG("font: height: %.2f, x-advance: %.2f",
term.fextents.height, term.fextents.max_x_advance);
assert(term.fextents.max_y_advance == 0);
/* Glyph cache */
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) {
struct font *f = &term.fonts[i];
for (int j = 0; j < 256; j++) {
cairo_glyph_t *glyphs = NULL;
int count = 0;
char c = j;
cairo_status_t status = cairo_scaled_font_text_to_glyphs(
f->font, 0, 0 + term.fextents.ascent,
&c, 1, &glyphs, &count,
NULL, NULL, NULL);
if (status != CAIRO_STATUS_SUCCESS)
continue;
if (count == 0)
continue;
assert(glyphs != NULL);
assert(count == 1);
f->glyph_cache[j].glyphs = glyphs;
f->glyph_cache[j].count = count;
}
}
term.wl.display = wl_display_connect(NULL);
if (term.wl.display == NULL) {
LOG_ERR("failed to connect to wayland; no compositor running?");
@ -916,15 +896,8 @@ out:
free(term.window_title);
tll_free_and_free(term.window_title_stack, free);
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) {
struct font *f = &term.fonts[i];
if (f->font != NULL)
cairo_scaled_font_destroy(f->font);
for (size_t j = 0; j < 256; j++)
cairo_glyph_free(f->glyph_cache[j].glyphs);
}
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++)
font_destroy(&term.fonts[i]);
if (term.flash.fd != -1)
close(term.flash.fd);
@ -944,4 +917,5 @@ out:
cairo_debug_reset_static_data();
return ret;
}

View file

@ -10,7 +10,7 @@ project('foot', 'c',
is_debug_build = get_option('buildtype').startswith('debug')
add_project_arguments(
['-D_GNU_SOURCE',
['-D_GNU_SOURCE=200809L',
#'-DF00SEL_VERSION=@0@'.format(version)] +
] +
(is_debug_build ? ['-D_DEBUG'] : []),
@ -23,7 +23,6 @@ math = cc.find_library('m')
threads = dependency('threads')
fontconfig = dependency('fontconfig')
cairo = dependency('cairo')
cairo_ft = dependency('cairo-ft')
wayland_protocols = dependency('wayland-protocols')
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
@ -78,7 +77,7 @@ executable(
'tllist.h',
'vt.c', 'vt.h',
wl_proto_src + wl_proto_headers,
dependencies: [threads, math, cairo, cairo_ft, fontconfig, wayland_client, wayland_cursor, xkb],
dependencies: [threads, math, cairo, fontconfig, wayland_client, wayland_cursor, xkb],
install: true)
custom_target(

View file

@ -13,6 +13,7 @@
#include "log.h"
#include "shm.h"
#include "grid.h"
#include "font.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
@ -59,15 +60,17 @@ gseq_flush(struct terminal *term, struct buffer *buf)
if (gseq.count == 0)
return;
assert(NULL);
struct rgb fg = color_hex_to_rgb(gseq.foreground);
if (gseq.attrs.dim)
color_dim(&fg);
#if 0
cairo_set_scaled_font(buf->cairo, attrs_to_font(term, &gseq.attrs)->font);
cairo_set_source_rgb(buf->cairo, fg.r, fg.g, fg.b);
cairo_show_glyphs(buf->cairo, gseq.glyphs, gseq.count);
#endif
gseq.g = gseq.glyphs;
gseq.count = 0;
}
@ -255,38 +258,30 @@ render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell,
gseq.foreground = _fg;
}
int new_glyphs
= sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0]) - gseq.count;
struct font *font = attrs_to_font(term, &cell->attrs);
struct glyph_cache *entry = cell->c[1] == '\0'
? &font->glyph_cache[(unsigned char)cell->c[0]]
: NULL;
if (likely(entry != NULL && entry->glyphs != NULL)) {
/* Copy cached glyph(s) and upate position */
memcpy(gseq.g, entry->glyphs, entry->count * sizeof(gseq.g[0]));
for (size_t i = 0; i < entry->count; i++) {
gseq.g[i].x += x;
gseq.g[i].y += y;
}
new_glyphs = entry->count;
} else {
/* Must generate new glyph(s) */
cairo_status_t status = cairo_scaled_font_text_to_glyphs(
font->font, x, y + term->fextents.ascent,
cell->c, strnlen(cell->c, 4), &gseq.g, &new_glyphs,
NULL, NULL, NULL);
if (status != CAIRO_STATUS_SUCCESS)
return;
struct glyph *glyph = NULL;
if (strnlen(cell->c, 4) == 1) {
if (font->cache[(unsigned char)cell->c[0]].surf != NULL)
glyph = &font->cache[(unsigned char)cell->c[0]];
}
gseq.g += new_glyphs;
gseq.count += new_glyphs;
assert(gseq.count <= sizeof(gseq.glyphs) / sizeof(gseq.glyphs[0]));
struct glyph _glyph;
if (glyph == NULL) {
if (!font_glyph_for_utf8(font, cell->c, &_glyph))
return;
glyph = &_glyph;
}
assert(glyph != NULL);
cairo_set_source_rgb(buf->cairo, fg.r, fg.g, fg.b);
cairo_set_operator(buf->cairo, CAIRO_OPERATOR_OVER);
cairo_mask_surface(buf->cairo, glyph->surf, x + glyph->left, y + term->fextents.ascent - glyph->top);
if (glyph == &_glyph) {
cairo_surface_destroy(_glyph.surf);
free(_glyph.data);
}
}
static void

View file

@ -6,6 +6,9 @@
#include <threads.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <cairo.h>
#include <wayland-client.h>
#include <primary-selection-unstable-v1.h>
@ -203,13 +206,18 @@ struct primary {
uint32_t serial;
};
struct glyph_cache {
cairo_glyph_t *glyphs;
int count;
struct glyph {
void *data;
cairo_surface_t *surf;
int left;
int top;
};
struct font {
cairo_scaled_font_t *font;
FT_Face face;
int load_flags;
int render_flags;
FT_LcdFilter lcd_filter;
struct {
double position;
double thickness;
@ -219,7 +227,8 @@ struct font {
double thickness;
} strikeout;
struct glyph_cache glyph_cache[256];
struct glyph cache[256];
mtx_t lock;
};
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR };
@ -321,7 +330,12 @@ struct terminal {
struct grid *grid;
struct font fonts[4];
cairo_font_extents_t fextents;
struct {
int height;
int descent;
int ascent;
int max_x_advance;
} fextents;
struct wayland wl;
struct {