diff --git a/font.c b/font.c index 49fd8130..c6a118c9 100644 --- a/font.c +++ b/font.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,6 +10,8 @@ #define LOG_MODULE "font" #include "log.h" +#define min(x, y) ((x) < (y) ? (x) : (y)) + static FT_Library ft_lib; static void __attribute__((constructor)) @@ -88,40 +91,128 @@ font_from_name(const char *name, struct font *font) if (ft_err != 0) LOG_ERR("%s: failed to create FreeType face", face_file); - /* TODO: use FT_Set_Char_Size() if FC_PIXEL_SIZE doesn't exist, and use size instead? */ - if ((ft_err = FT_Set_Pixel_Sizes(ft_face, 0, size)) != 0) - LOG_WARN("failed to set FreeType pixel sizes"); + 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, fc_antialias; + 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); + 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(const struct font *font, const char *utf8, +font_glyph_for_utf8(struct font *font, const char *utf8, struct glyph *glyph) { + mbstate_t ps = {0}; wchar_t wc; - if (mbstowcs(&wc, utf8, 1) < 0) + 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); - FT_Error err = FT_Load_Glyph(font->face, idx, FT_LOAD_DEFAULT); - if (err != 0) - return false; + 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, FT_RENDER_MODE_NORMAL); + err = FT_Render_Glyph(font->face->glyph, font->render_flags); if (err != 0) - return false; + goto err; assert(font->face->glyph->format == FT_GLYPH_FORMAT_BITMAP); @@ -136,17 +227,17 @@ font_glyph_for_utf8(const struct font *font, const char *utf8, 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; c++) { + 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 < 8; i++) { - reversed |= (v & 1) << (7 - i); - v >>= 1; - } + for (size_t i = 0; i < min(8, bitmap->width - c * 8); i++) + reversed |= ((v >> (7 - i)) & 1) << i; + data[r * stride + c] = reversed; } } @@ -163,7 +254,7 @@ font_glyph_for_utf8(const struct font *font, const char *utf8, LOG_ERR("unimplemented FreeType bitmap pixel mode: %d", bitmap->pixel_mode); free(data); - return false; + goto err; } cairo_surface_t *surf = cairo_image_surface_create_for_data( @@ -172,7 +263,7 @@ font_glyph_for_utf8(const struct font *font, const char *utf8, if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) { free(data); cairo_surface_destroy(surf); - return false; + goto err; } *glyph = (struct glyph){ @@ -180,8 +271,18 @@ font_glyph_for_utf8(const struct font *font, const char *utf8, .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 @@ -196,4 +297,6 @@ font_destroy(struct font *font) if (font->cache[i].data != NULL) free(font->cache[i].data); } + + mtx_destroy(&font->lock); } diff --git a/font.h b/font.h index c02e4ac1..192640a9 100644 --- a/font.h +++ b/font.h @@ -1,9 +1,11 @@ #pragma once #include +#include + #include "terminal.h" bool font_from_name(const char *name, struct font *result); bool font_glyph_for_utf8( - const struct font *font, const char *utf8, struct glyph *glyph); + struct font *font, const char *utf8, struct glyph *glyph); void font_destroy(struct font *font); diff --git a/meson.build b/meson.build index 95a139d7..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'] : []), diff --git a/terminal.h b/terminal.h index 89f3dc39..86b92d17 100644 --- a/terminal.h +++ b/terminal.h @@ -215,6 +215,9 @@ struct glyph { struct font { FT_Face face; + int load_flags; + int render_flags; + FT_LcdFilter lcd_filter; struct { double position; double thickness; @@ -225,6 +228,7 @@ struct font { } strikeout; struct glyph cache[256]; + mtx_t lock; }; enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; @@ -326,7 +330,6 @@ struct terminal { struct grid *grid; struct font fonts[4]; - //cairo_font_extents_t fextents; struct { int height; int descent;