diff --git a/PKGBUILD b/PKGBUILD index 4dd9ce1b..b3b4352c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -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') diff --git a/font.c b/font.c index 4d8bd65f..c6a118c9 100644 --- a/font.c +++ b/font.c @@ -2,46 +2,57 @@ #include #include +#include #include #include -#include #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); } diff --git a/font.h b/font.h index d2b8274c..192640a9 100644 --- a/font.h +++ b/font.h @@ -1,5 +1,11 @@ #pragma once -#include +#include +#include -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); diff --git a/main.c b/main.c index 8248e28a..7d0fa355 100644 --- a/main.c +++ b/main.c @@ -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; + } diff --git a/meson.build b/meson.build index a6da9aba..84fb288d 100644 --- a/meson.build +++ b/meson.build @@ -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( diff --git a/render.c b/render.c index 98babe38..cf6c013e 100644 --- a/render.c +++ b/render.c @@ -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 diff --git a/terminal.h b/terminal.h index 1971d3f4..86b92d17 100644 --- a/terminal.h +++ b/terminal.h @@ -6,6 +6,9 @@ #include +#include +#include FT_FREETYPE_H + #include #include #include @@ -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 {