diff --git a/config.c b/config.c index 5c6eb57..01a985d 100644 --- a/config.c +++ b/config.c @@ -67,7 +67,7 @@ conf_to_color(const struct yml_node *node) struct font * conf_to_font(const struct yml_node *node) { - return font_new(yml_value_as_string(node)); + return font_from_name(yml_value_as_string(node));; } struct deco * @@ -263,7 +263,7 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend) * and particles. This allows us to specify a default font and * foreground color at top-level. */ - struct font *font = font_new("sans"); + struct font *font = font_from_name("sans"); struct rgba foreground = (struct rgba){1.0, 1.0, 1.0, 1.0}; /* White */ const struct yml_node *font_node = yml_get_value(bar, "font"); diff --git a/font.c b/font.c index dba1a5b..d61fea6 100644 --- a/font.c +++ b/font.c @@ -1,29 +1,32 @@ #include "font.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #define LOG_MODULE "font" #define LOG_ENABLE_DBG 0 #include "log.h" -#include "tllist.h" +#include "stride.h" -struct font { - char *name; - cairo_scaled_font_t *scaled_font; - int ref_counter; -}; +#define min(x, y) ((x) < (y) ? (x) : (y)) -static tll(const struct font *) cache = tll_init(); +static FT_Library ft_lib; +static mtx_t ft_lock; + +static tll(const struct font *) font_cache = tll_init(); + +static const size_t glyph_cache_size = 512; static void __attribute__((constructor)) init(void) { FcInit(); + FT_Init_FreeType(&ft_lib); + mtx_init(&ft_lock, mtx_plain); #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG int raw_version = FcGetVersion(); @@ -40,16 +43,232 @@ init(void) static void __attribute__((destructor)) fini(void) { + assert(tll_length(font_cache) == 0); + + mtx_destroy(&ft_lock); + FT_Done_FreeType(ft_lib); FcFini(); - assert(tll_length(cache) == 0); +} + +static bool +font_destroy_no_free(struct font *font) +{ + assert(font->ref_counter > 0); + if (--font->ref_counter > 0) + return false; + + if (font->face != NULL) { + mtx_lock(&ft_lock); + FT_Done_Face(font->face); + mtx_unlock(&ft_lock); + } + + mtx_destroy(&font->lock); + + if (font->fc_pattern != NULL) + FcPatternDestroy(font->fc_pattern); + if (font->fc_fonts != NULL) + FcFontSetDestroy(font->fc_fonts); + + free(font->name); + + if (font->cache == NULL) + return true; + + for (size_t i = 0; i < glyph_cache_size; i++) { + if (font->cache[i] == NULL) + continue; + + tll_foreach(*font->cache[i], it) { + if (!it->item.valid) + continue; + + void *image = pixman_image_get_data(it->item.pix); + pixman_image_unref(it->item.pix); + free(image); + } + + tll_free(*font->cache[i]); + free(font->cache[i]); + } + free(font->cache); + + tll_foreach(font_cache, it) { + if (it->item == font) { + tll_remove(font_cache, it); + break; + } + } + + return true; +} + +static bool +from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx, + struct font *font, bool is_fallback) +{ + memset(font, 0, sizeof(*font)); + + FcChar8 *face_file = NULL; + FcPattern *final_pattern = NULL; + int font_idx = -1; + + for (int i = start_idx; i < fonts->nfont; i++) { + FcPattern *pat = FcFontRenderPrepare(NULL, pattern, fonts->fonts[i]); + assert(pat != NULL); + + if (FcPatternGetString(pat, FC_FT_FACE, 0, &face_file) != FcResultMatch) { + if (FcPatternGetString(pat, FC_FILE, 0, &face_file) != FcResultMatch) { + FcPatternDestroy(pat); + continue; + } + } + + final_pattern = pat; + font_idx = i; + break; + } + + assert(font_idx != -1); + assert(final_pattern != NULL); + + 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", face_file); + FcPatternDestroy(final_pattern); + return false; + } + + FcBool scalable; + if (FcPatternGetBool(final_pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = FcTrue; + + double pixel_fixup; + if (FcPatternGetDouble(final_pattern, "pixelsizefixupfactor", 0, &pixel_fixup) != FcResultMatch) + pixel_fixup = 1.; + + LOG_DBG("loading: %s", face_file); + + mtx_lock(&ft_lock); + FT_Face ft_face; + FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, 0, &ft_face); + mtx_unlock(&ft_lock); + 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 || fc_rgba == FC_RGBA_BGR) + load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD; + else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR) + load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD_V; + 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 (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR) + render_flags |= FT_RENDER_MODE_LCD; + else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR) + render_flags |= FT_RENDER_MODE_LCD_V; + 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); + + 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; + + mtx_init(&font->lock, mtx_plain); + font->face = ft_face; + font->load_flags = load_flags | FT_LOAD_COLOR; + font->render_flags = render_flags; + font->pixel_size_fixup = scalable ? pixel_fixup : 1.; + font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR; + font->fc_idx = font_idx; + font->is_fallback = is_fallback; + font->ref_counter = 1; + + font->fextents.height = height * font->pixel_size_fixup; + font->fextents.descent = -descent * font->pixel_size_fixup; + font->fextents.ascent = ascent * font->pixel_size_fixup; + font->fextents.max_x_advance = max_x_advance * font->pixel_size_fixup; + + LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d", + height, descent, ascent, max_x_advance); + + if (!is_fallback) { + font->fc_pattern = pattern; + font->fc_fonts = fonts; + font->cache = calloc(glyph_cache_size, sizeof(font->cache[0])); + } + + return true; } struct font * -font_new(const char *name) +font_from_name(const char *name) { - /* Check if font have already been loaded */ - tll_foreach(cache, it) { - if (strcmp(name, it->item->name) == 0) + LOG_DBG("instantiating %s", name); + + tll_foreach(font_cache, it) { + if (strcmp(it->item->name, name) == 0) return font_clone(it->item); } @@ -68,58 +287,273 @@ font_new(const char *name) FcDefaultSubstitute(pattern); FcResult result; - FcPattern *final_pattern = FcFontMatch(NULL, pattern, &result); - FcPatternDestroy(pattern); - - if (final_pattern == NULL) { + FcFontSet *fonts = FcFontSort(NULL, pattern, FcTrue, NULL, &result); + if (result != FcResultMatch) { LOG_ERR("%s: failed to match font", name); - return NULL; - } - - double font_size; - if (FcPatternGetDouble(final_pattern, FC_PIXEL_SIZE, 0, &font_size)) { - LOG_ERR("%s: failed to get size", name); - FcPatternDestroy(final_pattern); - return NULL; - } - - cairo_font_face_t *face = cairo_ft_font_face_create_for_pattern( - final_pattern); - - 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, font_size, font_size); - - cairo_font_options_t *options = cairo_font_options_create(); - 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); + FcPatternDestroy(pattern); return NULL; } struct font *font = malloc(sizeof(*font)); - font->name = strdup(name); - font->scaled_font = scaled_font; - font->ref_counter = 1; + if (!from_font_set(pattern, fonts, 0, font, false)) { + free(font); + FcFontSetDestroy(fonts); + FcPatternDestroy(pattern); + return NULL; + } - tll_push_back(cache, font); + font->name = strdup(name); + tll_push_back(font_cache, font); return font; } +static size_t +hash_index(wchar_t wc) +{ + return wc % glyph_cache_size; +} + +static bool +glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph) +{ + /* + * 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); + if (idx == 0) { + /* No glyph in this font, try fontconfig fallback fonts */ + + /* Try fontconfig fallback fonts */ + assert(font->fc_pattern != NULL); + assert(font->fc_fonts != NULL); + assert(font->fc_idx != -1); + + for (int i = font->fc_idx + 1; i < font->fc_fonts->nfont; i++) { + struct font fallback; + if (!from_font_set(font->fc_pattern, font->fc_fonts, i, &fallback, true)) + continue; + + if (glyph_for_wchar(&fallback, wc, glyph)) { + LOG_DBG("%C: used fontconfig fallback", wc); + font_destroy_no_free(&fallback); + return true; + } + + font_destroy_no_free(&fallback); + } + + LOG_WARN("%C: no glyph found (in neither the main font, " + "nor any fallback fonts)", 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; + if (bitmap->width == 0) + goto err; + + pixman_format_code_t pix_format; + int width; + int rows; + + switch (bitmap->pixel_mode) { + case FT_PIXEL_MODE_MONO: + pix_format = PIXMAN_a1; + width = bitmap->width; + rows = bitmap->rows; + break; + + case FT_PIXEL_MODE_GRAY: + pix_format = PIXMAN_a8; + width = bitmap->width; + rows = bitmap->rows; + break; + + case FT_PIXEL_MODE_LCD: + pix_format = PIXMAN_x8r8g8b8; + width = bitmap->width / 3; + rows = bitmap->rows; + break; + + case FT_PIXEL_MODE_LCD_V: + pix_format = PIXMAN_x8r8g8b8; + width = bitmap->width; + rows = bitmap->rows / 3; + break; + + case FT_PIXEL_MODE_BGRA: + pix_format = PIXMAN_a8r8g8b8; + width = bitmap->width; + rows = bitmap->rows; + break; + + default: + LOG_ERR("unimplemented: FT pixel mode: %d", bitmap->pixel_mode); + goto err; + break; + } + + int stride = stride_for_format_and_width(pix_format, width); + assert(stride >= bitmap->pitch); + + uint8_t *data = malloc(rows * stride); + + /* Convert FT bitmap to pixman image */ + 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; + + case FT_PIXEL_MODE_BGRA: + assert(stride == bitmap->pitch); + memcpy(data, bitmap->buffer, bitmap->rows * bitmap->pitch); + break; + + case FT_PIXEL_MODE_LCD: + for (size_t r = 0; r < bitmap->rows; r++) { + for (size_t c = 0; c < bitmap->width; c += 3) { + unsigned char _r = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 2 : 0)]; + unsigned char _g = bitmap->buffer[r * bitmap->pitch + c + 1]; + unsigned char _b = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 0 : 2)]; + + uint32_t *p = (uint32_t *)&data[r * stride + 4 * (c / 3)]; + *p = _r << 16 | _g << 8 | _b; + } + } + break; + + case FT_PIXEL_MODE_LCD_V: + /* Unverified */ + for (size_t r = 0; r < bitmap->rows; r += 3) { + for (size_t c = 0; c < bitmap->width; c++) { + unsigned char _r = bitmap->buffer[(r + (font->bgr ? 2 : 0)) * bitmap->pitch + c]; + unsigned char _g = bitmap->buffer[(r + 1) * bitmap->pitch + c]; + unsigned char _b = bitmap->buffer[(r + (font->bgr ? 0 : 2)) * bitmap->pitch + c]; + + uint32_t *p = (uint32_t *)&data[r / 3 * stride + 4 * c]; + *p = _r << 16 | _g << 8 | _b; + } + } + break; + + default: + abort(); + break; + } + + pixman_image_t *pix = pixman_image_create_bits_no_clear( + pix_format, width, rows, (uint32_t *)data, stride); + + if (pix == NULL) { + free(data); + goto err; + } + + pixman_image_set_component_alpha( + pix, + bitmap->pixel_mode == FT_PIXEL_MODE_LCD || + bitmap->pixel_mode == FT_PIXEL_MODE_LCD_V); + + if (font->pixel_size_fixup != 1.) { + struct pixman_transform scale; + pixman_transform_init_scale( + &scale, + pixman_double_to_fixed(1. / font->pixel_size_fixup), + pixman_double_to_fixed(1. / font->pixel_size_fixup)); + pixman_image_set_transform(pix, &scale); + } + + *glyph = (struct glyph){ + .wc = wc, + .cols = wcwidth(wc), + .pix = pix, + .x = font->face->glyph->bitmap_left / font->pixel_size_fixup, + .y = font->face->glyph->bitmap_top * font->pixel_size_fixup, + .x_advance = font->face->glyph->advance.x / 64, + .width = width, + .height = rows, + .valid = true, + }; + + return true; + +err: + *glyph = (struct glyph){ + .wc = wc, + .valid = false, + }; + return false; +} + +const struct glyph * +font_glyph_for_wc(struct font *font, wchar_t wc) +{ + mtx_lock(&font->lock); + + assert(font->cache != NULL); + size_t hash_idx = hash_index(wc); + hash_entry_t *hash_entry = font->cache[hash_idx]; + + if (hash_entry != NULL) { + tll_foreach(*hash_entry, it) { + if (it->item.wc == wc) { + mtx_unlock(&font->lock); + return it->item.valid ? &it->item : NULL; + } + } + } + + struct glyph glyph; + bool got_glyph = glyph_for_wchar(font, wc, &glyph); + + if (hash_entry == NULL) { + hash_entry = calloc(1, sizeof(*hash_entry)); + + assert(font->cache[hash_idx] == NULL); + font->cache[hash_idx] = hash_entry; + } + + assert(hash_entry != NULL); + tll_push_back(*hash_entry, glyph); + + mtx_unlock(&font->lock); + return got_glyph ? &tll_back(*hash_entry) : NULL; +} + struct font * font_clone(const struct font *_font) { @@ -131,33 +565,6 @@ font_clone(const struct font *_font) void font_destroy(struct font *font) { - if (font == NULL) - return; - - assert(font->ref_counter > 0); - if (--font->ref_counter > 0) - return; - - tll_foreach(cache, it) { - if (it->item == font) { - tll_remove(cache, it); - break; - } - } - - cairo_scaled_font_destroy(font->scaled_font); - free(font->name); - free(font); -} - -const char * -font_face(const struct font *font) -{ - return font->name; -} - -cairo_scaled_font_t * -font_scaled_font(const struct font *font) -{ - return font->scaled_font; + if (font_destroy_no_free(font)) + free(font); } diff --git a/font.h b/font.h index 45c8987..3953ac9 100644 --- a/font.h +++ b/font.h @@ -1,15 +1,71 @@ #pragma once #include -#include +#include -#include "color.h" +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H +#include +#include -struct font; +#include "tllist.h" -struct font *font_new(const char *name); +struct glyph { + wchar_t wc; + int cols; + + pixman_image_t *pix; + int x; + int y; + int x_advance; + int width; + int height; + + bool valid; +}; + +typedef tll(struct glyph) hash_entry_t; + +struct font { + char *name; + + FcPattern *fc_pattern; + FcFontSet *fc_fonts; + int fc_idx; + + FT_Face face; + int load_flags; + int render_flags; + FT_LcdFilter lcd_filter; + double pixel_size_fixup; /* Scale factor - should only be used with ARGB32 glyphs */ + bool bgr; /* True for FC_RGBA_BGR and FC_RGBA_VBGR */ + + struct { + int height; + int descent; + int ascent; + int max_x_advance; + } fextents; + + struct { + int position; + int thickness; + } underline; + + struct { + int position; + int thickness; + } strikeout; + + hash_entry_t **cache; + mtx_t lock; + + bool is_fallback; + int ref_counter; +}; + +struct font *font_from_name(const char *name); struct font *font_clone(const struct font *font); +const struct glyph *font_glyph_for_wc(struct font *font, wchar_t wc); void font_destroy(struct font *font); - -const char *font_face(const struct font *font); -cairo_scaled_font_t *font_scaled_font(const struct font *font); diff --git a/meson.build b/meson.build index 34c9c95..f1014a7 100644 --- a/meson.build +++ b/meson.build @@ -36,7 +36,9 @@ endif cc = meson.get_compiler('c') dl = cc.find_library('dl') threads = dependency('threads') +freetype = dependency('freetype2') fontconfig = dependency('fontconfig') +pixman = dependency('pixman-1') cairo = dependency('cairo') cairo_ft = dependency('cairo-ft') yaml = dependency('yaml-0.1') @@ -104,7 +106,7 @@ f00bar = executable( 'tag.c', 'tag.h', 'tllist.h', 'yml.c', 'yml.h', - dependencies: [bar, cairo, cairo_ft, fontconfig, yaml, threads, dl] + + dependencies: [bar, freetype, fontconfig, pixman, cairo, cairo_ft, yaml, threads, dl] + decorations + particles + modules, build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', export_dynamic: true, @@ -122,6 +124,7 @@ install_headers( 'log.h', 'module.h', 'particle.h', + 'stride.h', 'tag.h', 'tllist.h', 'yml.h', diff --git a/particles/string.c b/particles/string.c index a276c75..e4818dc 100644 --- a/particles/string.c +++ b/particles/string.c @@ -19,13 +19,8 @@ struct eprivate { /* Set when instantiating */ char *text; - /* Set in begin_expose() */ - cairo_font_extents_t fextents; - cairo_glyph_t *glyphs; - cairo_text_cluster_t *clusters; - cairo_text_cluster_flags_t cluster_flags; + const struct glyph **glyphs; int num_glyphs; - int num_clusters; }; static void @@ -34,11 +29,7 @@ exposable_destroy(struct exposable *exposable) struct eprivate *e = exposable->private; free(e->text); - if (e->glyphs != NULL) - cairo_glyph_free(e->glyphs); - if (e->clusters != NULL) - cairo_text_cluster_free(e->clusters); - + free(e->glyphs); free(e); exposable_default_destroy(exposable); } @@ -48,48 +39,58 @@ begin_expose(struct exposable *exposable, cairo_t *cr) { struct eprivate *e = exposable->private; - cairo_scaled_font_t *scaled = font_scaled_font(exposable->particle->font); - - cairo_set_scaled_font(cr, scaled); - cairo_font_extents(cr, &e->fextents); + struct font *font = exposable->particle->font; LOG_DBG("%s: ascent=%f, descent=%f, height=%f", - font_face(exposable->particle->font), - e->fextents.ascent, e->fextents.descent, e->fextents.height); + font->name, font->fextents.ascent, + font->fextents.descent, font->fextents.height); - cairo_status_t status = cairo_scaled_font_text_to_glyphs( - scaled, 0, 0, e->text, -1, &e->glyphs, &e->num_glyphs, - &e->clusters, &e->num_clusters, &e->cluster_flags); + size_t chars = mbstowcs(NULL, e->text, 0); + wchar_t wtext[chars + 1]; + mbstowcs(wtext, e->text, chars + 1); - if (status != CAIRO_STATUS_SUCCESS) { - LOG_WARN("failed to convert \"%s\" to glyphs: %s", - e->text, cairo_status_to_string(status)); + e->glyphs = malloc(chars * sizeof(e->glyphs[0])); + e->num_glyphs = 0; - e->num_glyphs = -1; - e->num_clusters = -1; - memset(&e->fextents, 0, sizeof(e->fextents)); - exposable->width = 0; - } else { - cairo_text_extents_t extents; - cairo_scaled_font_glyph_extents( - scaled, e->glyphs, e->num_glyphs, &extents); - - exposable->width = (exposable->particle->left_margin + - extents.x_advance + - exposable->particle->right_margin); + /* Convert text to glyph masks/images. */ + for (size_t i = 0; i < chars; i++) { + const struct glyph *glyph = font_glyph_for_wc(font, wtext[i]); + if (glyph == NULL) + continue; + e->glyphs[e->num_glyphs++] = glyph; } + exposable->width = exposable->particle->left_margin + + exposable->particle->right_margin; + + /* Calculate the size we need to render the glyphs */ + for (int i = 0; i < e->num_glyphs; i++) + exposable->width += e->glyphs[i]->x_advance; + return exposable->width; } +static inline pixman_color_t +color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha) +{ + int alpha_div = 0xffff / alpha; + return (pixman_color_t){ + .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div, + .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) / alpha_div, + .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) / alpha_div, + .alpha = alpha, + }; +} + static void expose(const struct exposable *exposable, cairo_t *cr, int x, int y, int height) { exposable_render_deco(exposable, cr, x, y, height); const struct eprivate *e = exposable->private; + const struct font *font = exposable->particle->font; - if (e->num_glyphs == -1) + if (e->num_glyphs == 0) return; /* @@ -108,63 +109,55 @@ expose(const struct exposable *exposable, cairo_t *cr, int x, int y, int height) * font family. */ const double baseline = (double)y + - (double)(height + e->fextents.ascent + e->fextents.descent) / 2.0 - - (e->fextents.descent > 0 ? e->fextents.descent : 0); + (double)(height + font->fextents.ascent + font->fextents.descent) / 2.0 - + (font->fextents.descent > 0 ? font->fextents.descent : 0); - /* Adjust glyph offsets */ + /* TODO: get rid of cairo?... */ + cairo_surface_t *surf = cairo_get_target(cr); + cairo_surface_flush(surf); + + pixman_image_t *pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, + cairo_image_surface_get_width(surf), + cairo_image_surface_get_height(surf), + (uint32_t *)cairo_image_surface_get_data(surf), + cairo_image_surface_get_stride(surf)); + + uint32_t hex_color = + (uint32_t)(uint8_t)(exposable->particle->foreground.red * 255) << 16 | + (uint32_t)(uint8_t)(exposable->particle->foreground.green * 255) << 8 | + (uint32_t)(uint8_t)(exposable->particle->foreground.blue * 255) << 0; + uint16_t alpha = exposable->particle->foreground.alpha * 65535; + pixman_color_t fg = color_hex_to_pixman_with_alpha(hex_color, alpha); + + x += exposable->particle->left_margin; + + /* Loop glyphs and render them, one by one */ for (int i = 0; i < e->num_glyphs; i++) { - e->glyphs[i].x += x + exposable->particle->left_margin; - e->glyphs[i].y += baseline; + const struct glyph *glyph = e->glyphs[i]; + assert(glyph != NULL); + + if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) { + /* Glyph surface is a pre-rendered image (typically a color emoji...) */ + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0, + x + glyph->x, baseline - glyph->y, + glyph->width, glyph->height); + } else { + /* Glyph surface is an alpha mask */ + pixman_image_t *src = pixman_image_create_solid_fill(&fg); + pixman_image_composite32( + PIXMAN_OP_OVER, src, glyph->pix, pix, 0, 0, 0, 0, + x + glyph->x, baseline - glyph->y, + glyph->width, glyph->height); + pixman_image_unref(src); + } + + x += glyph->x_advance; } - cairo_scaled_font_t *scaled = font_scaled_font(exposable->particle->font); - cairo_set_scaled_font(cr, scaled); - cairo_set_source_rgba(cr, - exposable->particle->foreground.red, - exposable->particle->foreground.green, - exposable->particle->foreground.blue, - exposable->particle->foreground.alpha); - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - - cairo_show_text_glyphs( - cr, e->text, -1, e->glyphs, e->num_glyphs, - e->clusters, e->num_clusters, e->cluster_flags); - -#if 0 - cairo_text_extents_t extents; - cairo_scaled_font_glyph_extents(scaled, e->glyphs, e->num_glyphs, &extents); - - /* Bar center */ - cairo_set_line_width(cr, 1); - cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); - cairo_move_to(cr, x, (double)y + (double)height / 2 + 0.5); - cairo_line_to(cr, x + extents.x_advance, (double)y + (double)height / 2 + 0.5); - cairo_stroke(cr); - - /* Ascent */ - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); - cairo_move_to(cr, x, baseline - e->fextents.ascent + 0.5); - cairo_line_to(cr, x + extents.x_advance, baseline - e->fextents.ascent + 0.5); - cairo_stroke(cr); - - /* Descent */ - cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); - cairo_move_to(cr, x, baseline + e->fextents.descent + 0.5); - cairo_line_to(cr, x + extents.x_advance, baseline + e->fextents.descent + 0.5); - cairo_stroke(cr); - - /* Height (!= ascent + descent) */ - cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); - cairo_move_to(cr, x - 3 + 0.5, (double)y + (double)(height - e->fextents.height) / 2); - cairo_line_to(cr, x - 3 + 0.5, (double)y + (double)(height + e->fextents.height) / 2); - cairo_stroke(cr); - - /* Height (ascent + descent) */ - cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); - cairo_move_to(cr, x - 1 + 0.5, (double)y + (double)(height - (e->fextents.ascent + e->fextents.descent)) / 2); - cairo_line_to(cr, x - 1 + 0.5, (double)y + (double)(height + (e->fextents.ascent + e->fextents.descent)) / 2); - cairo_stroke(cr); -#endif + pixman_image_unref(pix); + cairo_surface_mark_dirty(surf); } static struct exposable * @@ -174,8 +167,8 @@ instantiate(const struct particle *particle, const struct tag_set *tags) struct eprivate *e = calloc(1, sizeof(*e)); e->text = tags_expand_template(p->text, tags); - e->num_glyphs = -1; - e->num_clusters = -1; + e->glyphs = NULL; + e->num_glyphs = 0; if (p->max_len > 0) { const size_t len = strlen(e->text); diff --git a/stride.h b/stride.h new file mode 100644 index 0000000..b2c71a7 --- /dev/null +++ b/stride.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +static inline int +stride_for_format_and_width(pixman_format_code_t format, int width) +{ + return (((PIXMAN_FORMAT_BPP(format) * width + 7) / 8 + 4 - 1) & -4); +}