diff options
-rw-r--r-- | src/plugins/controls/label-freetype/plugin.c | 485 |
1 files changed, 310 insertions, 175 deletions
diff --git a/src/plugins/controls/label-freetype/plugin.c b/src/plugins/controls/label-freetype/plugin.c index 7cf9ce73..d20f14f1 100644 --- a/src/plugins/controls/label-freetype/plugin.c +++ b/src/plugins/controls/label-freetype/plugin.c @@ -22,6 +22,8 @@ * Written by: Fabian Vogt <fvogt@suse.com> */ +#include <assert.h> +#include <endian.h> #include <stdio.h> #include <stdint.h> #include <string.h> @@ -41,6 +43,37 @@ #define FONT_FALLBACK "/usr/share/fonts/Plymouth.ttf" #define MONOSPACE_FONT_FALLBACK "/usr/share/fonts/Plymouth-monospace.ttf" +/* This is a little sketchy... It relies on implementation details of the compiler + * but it makes dealing with the fixed point math freetype uses much more pleasant, + * imo, so I'm going to roll with it for now until it causes problems. + */ +typedef union +{ + struct + { +#if BYTE_ORDER == LITTLE_ENDIAN + uint32_t fractional_part : 6; + uint32_t pixels : 26; +#else + uint32_t pixels : 26; + uint32_t fractional_part : 6; +#endif + } as_pixels_unit; + + struct + { +#if BYTE_ORDER == LITTLE_ENDIAN + uint32_t fractional_part : 6; + uint32_t points : 26; +#else + uint32_t points : 26; + uint32_t fractional_part : 6; +#endif + } as_points_unit; + + uint32_t as_integer; +} ply_freetype_unit_t; + struct _ply_label_plugin_control { ply_pixel_display_t *display; @@ -51,20 +84,37 @@ struct _ply_label_plugin_control FT_Library library; FT_Face face; + char *font; char *text; ply_rich_text_t *rich_text; ply_rich_text_span_t span; + + ply_array_t *dimensions_of_lines; + float red; float green; float blue; float alpha; + uint32_t scale_factor; + uint32_t is_hidden : 1; uint32_t is_monospaced : 1; }; +typedef enum +{ + PLY_LOAD_GLYPH_ACTION_MEASURE, + PLY_LOAD_GLYPH_ACTION_RENDER, +} ply_load_glyph_action_t; + ply_label_plugin_interface_t *ply_label_plugin_get_interface (void); +static void set_font_for_control (ply_label_plugin_control_t *label, + const char *font); +static void load_glyphs (ply_label_plugin_control_t *label, + ply_load_glyph_action_t action, + ply_pixel_buffer_t *pixel_buffer); /* Query fontconfig, if available, for the default font. */ static const char * @@ -124,13 +174,14 @@ create_control (void) { int error; ply_label_plugin_control_t *label; - const char *font_path; label = calloc (1, sizeof(ply_label_plugin_control_t)); label->is_hidden = true; label->width = -1; label->text = NULL; + label->scale_factor = 1; + label->dimensions_of_lines = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_POINTER); error = FT_Init_FreeType (&label->library); if (error) { @@ -138,33 +189,37 @@ create_control (void) return NULL; } - font_path = query_fc_match (); - error = set_font_with_fallback (label, font_path, FONT_FALLBACK); - if (error) { - FT_Done_FreeType (label->library); - free (label); - return NULL; - } - - /* 12pt/96dpi as default */ - error = FT_Set_Char_Size (label->face, 12 << 6, 0, 96, 0); - if (error) { - FT_Done_Face (label->face); - FT_Done_FreeType (label->library); - free (label); - return NULL; - } + set_font_for_control (label, "Sans"); return label; } static void +clear_dimensions_of_lines (ply_label_plugin_control_t *label) +{ + ply_rectangle_t **dimensions_of_lines; + size_t i; + + if (label->dimensions_of_lines == NULL) + return; + + dimensions_of_lines = (ply_rectangle_t **) ply_array_steal_pointer_elements (label->dimensions_of_lines); + for (i = 0; dimensions_of_lines[i] != NULL; i++) { + free (dimensions_of_lines[i]); + } +} + +static void destroy_control (ply_label_plugin_control_t *label) { if (label == NULL) return; + clear_dimensions_of_lines (label); + ply_array_free (label->dimensions_of_lines); + free (label->text); + free (label->font); FT_Done_Face (label->face); FT_Done_FreeType (label->library); @@ -183,14 +238,15 @@ get_height_of_control (ply_label_plugin_control_t *label) return label->area.height; } -static bool -load_character (ply_label_plugin_control_t *label, - const char **text) +static FT_GlyphSlot +load_glyph (ply_label_plugin_control_t *label, + ply_load_glyph_action_t action, + const char *input_text) { FT_Error error; size_t character_size; wchar_t character; - const char *input_text = *text; + FT_Int32 load_flags = FT_LOAD_TARGET_LIGHT; character_size = mbrtowc (&character, input_text, PLY_UTF8_CHARACTER_SIZE_MAX, NULL); @@ -199,100 +255,27 @@ load_character (ply_label_plugin_control_t *label, character_size = 1; } - error = FT_Load_Char (label->face, (FT_ULong) character, FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT); - - *text = input_text + character_size; + if (action == PLY_LOAD_GLYPH_ACTION_RENDER) + load_flags |= FT_LOAD_RENDER; - return !error; -} + error = FT_Load_Char (label->face, (FT_ULong) character, load_flags); -static FT_Int -width_of_string (ply_label_plugin_control_t *label, - const char *text) -{ - FT_Int width = 0; - FT_Int left_bearing = 0; - - while (*text != '\0' && *text != '\n') { - if (load_character (label, &text)) { - width += label->face->glyph->advance.x >> 6; - left_bearing = label->face->glyph->bitmap_left; - /* We don't "go back" when drawing, so when left bearing is - * negative (like for 'j'), we simply add to the width. */ - if (left_bearing < 0) - width += -left_bearing; - } - } + if (error) + return NULL; - return width; + return label->face->glyph; } static void size_control (ply_label_plugin_control_t *label) { - FT_Int character_width, line_width; - ply_rich_text_iterator_t rich_text_iterator; - ply_utf8_string_iterator_t utf8_string_iterator; - bool should_stop = false; - if (label->rich_text == NULL && label->text == NULL) { label->area.width = 0; label->area.height = 0; return; } - if (label->rich_text != NULL) { - ply_rich_text_iterator_initialize (&rich_text_iterator, - label->rich_text, - &label->span); - } else { - ply_utf8_string_iterator_initialize (&utf8_string_iterator, - label->text, - 0, - ply_utf8_string_get_length (label->text, strlen (label->text))); - } - label->area.width = 0; - label->area.height = 0; - - line_width = 0; - - /* Go through each line */ - do { - const char *current_character; - - if (label->rich_text != NULL) { - ply_rich_text_character_t *rich_text_character; - should_stop = !ply_rich_text_iterator_next (&rich_text_iterator, - &rich_text_character); - if (!should_stop) - current_character = rich_text_character->bytes; - } else { - size_t character_size; - - should_stop = !ply_utf8_string_iterator_next (&utf8_string_iterator, - ¤t_character, - &character_size); - } - - if (!should_stop) { - character_width = width_of_string (label, current_character); - line_width += character_width; - } - - if (should_stop || character_width == 0) { - if ((uint32_t) line_width > label->area.width) - label->area.width = line_width; - - line_width = 0; - - label->area.height += (label->face->size->metrics.ascender - - label->face->size->metrics.descender) >> 6; - } - } while (!should_stop); - - /* If centered, area.x is not the origin anymore */ - if ((long) label->area.width < label->width) - label->area.width = label->width; + load_glyphs (label, PLY_LOAD_GLYPH_ACTION_MEASURE, NULL); } static void @@ -420,35 +403,99 @@ look_up_rgb_color_from_terminal_color (ply_label_plugin_control_t *label, } static void -draw_control (ply_label_plugin_control_t *label, - ply_pixel_buffer_t *pixel_buffer, - long x, - long y, - unsigned long width, - unsigned long height) +update_scale_factor_from_pixel_buffer (ply_label_plugin_control_t *label, + ply_pixel_buffer_t *pixel_buffer) +{ + uint32_t device_scale; + + device_scale = ply_pixel_buffer_get_device_scale (pixel_buffer); + + if (label->scale_factor == device_scale) + return; + + label->scale_factor = device_scale; + set_font_for_control (label, label->font?: "Sans"); + size_control (label); +} + +static void +finish_measuring_line (ply_label_plugin_control_t *label, + ply_freetype_unit_t *glyph_x, + ply_freetype_unit_t *glyph_y, + ply_rectangle_t *dimensions) +{ + + ply_freetype_unit_t line_height; + ply_rectangle_t *entry; + + line_height.as_integer = label->face->size->metrics.ascender + -label->face->size->metrics.descender; + + dimensions->x = label->area.x; + + dimensions->width = glyph_x->as_pixels_unit.pixels - dimensions->x; + label->area.width = MAX (label->area.width, dimensions->width); + + dimensions->height = line_height.as_pixels_unit.pixels; + label->area.height += dimensions->height; + + entry = calloc (1, sizeof(ply_rectangle_t)); + *entry = *dimensions; + ply_array_add_pointer_element (label->dimensions_of_lines, entry); + + dimensions->y += dimensions->height; +} + +static void +align_lines (ply_label_plugin_control_t *label) { - FT_Vector pen; - FT_GlyphSlot slot; + ply_rectangle_t **dimensions_of_lines; + ply_rectangle_t *line_dimensions; + long width; + size_t i; + + if (label->alignment == PLY_LABEL_ALIGN_LEFT) + return; + + if (label->dimensions_of_lines == NULL) + return; + + width = label->width > 0? label->width : label->area.width; + + dimensions_of_lines = (ply_rectangle_t **) ply_array_get_pointer_elements (label->dimensions_of_lines); + + for (i = 0; dimensions_of_lines[i] != NULL; i++) { + line_dimensions = dimensions_of_lines[i]; + + if (label->alignment == PLY_LABEL_ALIGN_CENTER) + line_dimensions->x += (width - line_dimensions->width) / 2; + else if (label->alignment == PLY_LABEL_ALIGN_RIGHT) + line_dimensions->x += width - line_dimensions->width; + } +} + +static void +load_glyphs (ply_label_plugin_control_t *label, + ply_load_glyph_action_t action, + ply_pixel_buffer_t *pixel_buffer) +{ + FT_GlyphSlot glyph = NULL; ply_rich_text_iterator_t rich_text_iterator; ply_utf8_string_iterator_t utf8_string_iterator; - uint32_t *target; + uint32_t *target = NULL; ply_rectangle_t target_size; - if (label->is_hidden) - return; + ply_freetype_unit_t glyph_x = { .as_pixels_unit = { .pixels = label->area.x } }; + ply_freetype_unit_t glyph_y = { .as_pixels_unit = { .pixels = label->area.y } }; + FT_Error error; + FT_UInt previous_glyph_index = 0; + bool is_first_character = true; + ply_rectangle_t *line_dimensions = NULL; + ply_rectangle_t **dimensions_of_lines = NULL; + size_t line_number; if (label->rich_text == NULL && label->text == NULL) return; - /* Check for overlap. - * TODO: Don't redraw everything if only a part should be drawn! */ - if (label->area.x > x + (long) width || label->area.y > y + (long) height - || label->area.x + (long) label->area.width < x - || label->area.y + (long) label->area.height < y) - return; - - slot = label->face->glyph; - if (label->rich_text != NULL) { ply_rich_text_iterator_initialize (&rich_text_iterator, label->rich_text, @@ -460,17 +507,37 @@ draw_control (ply_label_plugin_control_t *label, ply_utf8_string_get_length (label->text, strlen (label->text))); } - target = ply_pixel_buffer_get_argb32_data (pixel_buffer); - ply_pixel_buffer_get_size (pixel_buffer, &target_size); + if (action == PLY_LOAD_GLYPH_ACTION_MEASURE) { + clear_dimensions_of_lines (label); + + line_dimensions = alloca (sizeof(ply_rectangle_t)); + line_dimensions->x = label->area.x; + line_dimensions->y = label->area.y; + label->area.width = 0; + label->area.height = 0; + } else if (ply_array_get_size (label->dimensions_of_lines) == 0) { + return; + } else { + dimensions_of_lines = (ply_rectangle_t **) ply_array_get_pointer_elements (label->dimensions_of_lines); + line_number = 0; - if (target_size.height == 0) - return; /* This happens sometimes. */ + line_dimensions = dimensions_of_lines[line_number++]; - /* 64ths of a pixel */ - pen.y = label->area.y << 6; + assert (line_dimensions != NULL); + + glyph_x.as_pixels_unit.pixels = line_dimensions->x; + } + + if (action == PLY_LOAD_GLYPH_ACTION_RENDER) { + target = ply_pixel_buffer_get_argb32_data (pixel_buffer); + ply_pixel_buffer_get_size (pixel_buffer, &target_size); + + if (target_size.height == 0) + return; + } /* Make sure that the first row fits */ - pen.y += label->face->size->metrics.ascender; + glyph_y.as_integer += label->face->size->metrics.ascender; /* Go through each line */ do { @@ -478,12 +545,14 @@ draw_control (ply_label_plugin_control_t *label, const char *current_character; uint8_t red, green, blue; - FT_Int extraAdvance = 0, positiveBearingX = 0; + FT_Int extra_advance = 0, positive_bearing_x = 0; ply_rich_text_character_t *rich_text_character; - red = 255 * label->red; - green = 255 * label->green; - blue = 255 * label->blue; + if (action == PLY_LOAD_GLYPH_ACTION_RENDER) { + red = 255 * label->red; + green = 255 * label->green; + blue = 255 * label->blue; + } if (label->rich_text != NULL) { should_stop = !ply_rich_text_iterator_next (&rich_text_iterator, @@ -493,32 +562,40 @@ draw_control (ply_label_plugin_control_t *label, current_character = rich_text_character->bytes; - look_up_rgb_color_from_terminal_color (label, - rich_text_character->style.foreground_color, - &red, - &green, - &blue); + if (action == PLY_LOAD_GLYPH_ACTION_RENDER) { + look_up_rgb_color_from_terminal_color (label, + rich_text_character->style.foreground_color, + &red, + &green, + &blue); + } } else { size_t character_size; should_stop = !ply_utf8_string_iterator_next (&utf8_string_iterator, ¤t_character, &character_size); + if (should_stop) + break; } - if (*current_character == '\n') + glyph = load_glyph (label, action, current_character); + + if (glyph == NULL) continue; - pen.x = label->area.x << 6; + if (*current_character == '\n') { + if (action == PLY_LOAD_GLYPH_ACTION_MEASURE) + finish_measuring_line (label, &glyph_x, &glyph_y, line_dimensions); + else + line_dimensions = dimensions_of_lines[line_number++]; - /* Start at start position (alignment) */ - if (label->alignment == PLY_LABEL_ALIGN_CENTER) - pen.x += (label->area.width - width_of_string (label, current_character)) << 5; - else if (label->alignment == PLY_LABEL_ALIGN_RIGHT) - pen.x += (label->area.width - width_of_string (label, current_character)) << 6; + glyph_x.as_pixels_unit.pixels = line_dimensions->x; + glyph_y.as_pixels_unit.pixels = line_dimensions->y; - if (!load_character (label, ¤t_character)) + glyph_y.as_integer += label->face->size->metrics.ascender; continue; + } /* We consider negative left bearing an increment in size, * as we draw full character boxes and don't "go back" in @@ -526,24 +603,69 @@ draw_control (ply_label_plugin_control_t *label, * For definitions see * https://freetype.org/freetype2/docs/glyphs/glyphs-3.html */ - if (slot->bitmap_left < 0) - extraAdvance = -slot->bitmap_left; + if (glyph->bitmap_left < 0) + extra_advance = -glyph->bitmap_left; else - positiveBearingX = slot->bitmap_left; + positive_bearing_x = glyph->bitmap_left; + + if (action == PLY_LOAD_GLYPH_ACTION_RENDER) { + draw_bitmap (label, target, target_size, &glyph->bitmap, + glyph_x.as_pixels_unit.pixels + positive_bearing_x, + glyph_y.as_pixels_unit.pixels - glyph->bitmap_top, + red, + green, + blue); + } + + glyph_x.as_integer += glyph->advance.x + extra_advance; - draw_bitmap (label, target, target_size, &slot->bitmap, - (pen.x >> 6) + positiveBearingX, - (pen.y >> 6) - slot->bitmap_top, - red, - green, - blue); + if (!is_first_character) { + FT_Vector kerning_space; - pen.x += slot->advance.x + extraAdvance; - pen.y += slot->advance.y; + error = FT_Get_Kerning (label->face, previous_glyph_index, glyph->glyph_index, FT_KERNING_DEFAULT, &kerning_space); - /* Next line */ - pen.y += label->face->size->metrics.height; + if (error == 0) + glyph_x.as_integer += kerning_space.x; + + previous_glyph_index = glyph->glyph_index; + } else { + is_first_character = false; + } } while (true); + + if (action == PLY_LOAD_GLYPH_ACTION_MEASURE) { + if (!is_first_character) + finish_measuring_line (label, &glyph_x, &glyph_y, line_dimensions); + + align_lines (label); + } +} + +static void +draw_control (ply_label_plugin_control_t *label, + ply_pixel_buffer_t *pixel_buffer, + long x, + long y, + unsigned long width, + unsigned long height) +{ + if (label->is_hidden) + return; + + if (label->rich_text == NULL && + label->text == NULL) + return; + + update_scale_factor_from_pixel_buffer (label, pixel_buffer); + + /* Check for overlap. + * TODO: Don't redraw everything if only a part should be drawn! */ + if (label->area.x > x + (long) width || label->area.y > y + (long) height + || label->area.x + (long) label->area.width < x + || label->area.y + (long) label->area.height < y) + return; + + load_glyphs (label, PLY_LOAD_GLYPH_ACTION_RENDER, pixel_buffer); } static void @@ -578,6 +700,8 @@ clear_text (ply_label_plugin_control_t *label) label->span.offset = 0; label->span.range = 0; } + + clear_dimensions_of_lines (label); } static void @@ -607,51 +731,62 @@ set_rich_text_for_control (ply_label_plugin_control_t *label, static void set_font_for_control (ply_label_plugin_control_t *label, - const char *fontdesc) + const char *font) { /* Only able to set size and monospaced/nonmonospaced */ int error = 0; char *size_str_after; const char *size_str, *font_path; - unsigned long size; - bool size_in_pixels; + char *new_font; + ply_freetype_unit_t size = { .as_points_unit = { .points = 12 } }; + int dpi = 96; + bool size_in_pixels = false; + + new_font = strdup (font); + free (label->font); + label->font = new_font; - if (strstr (fontdesc, "Mono") || strstr (fontdesc, "mono")) { - if (label->is_monospaced == false) { + if (strstr (font, "Mono") || strstr (font, "mono")) { + if (!label->is_monospaced) { FT_Done_Face (label->face); font_path = query_fc_match_monospace (); error = set_font_with_fallback (label, font_path, MONOSPACE_FONT_FALLBACK); + label->is_monospaced = true; } } else { - if (label->is_monospaced == true) { + if (label->is_monospaced || label->face == NULL) { FT_Done_Face (label->face); font_path = query_fc_match (); error = set_font_with_fallback (label, font_path, FONT_FALLBACK); + label->is_monospaced = false; } } if (error) FT_Done_Face (label->face); - size = 25; /* Default, if not set. */ - size_in_pixels = false; - /* Format is "Family 1[,Family 2[,..]] [25[px]]" . * [] means optional. */ - size_str = strrchr (fontdesc, ' '); + size_str = strrchr (font, ' '); if (size_str) { - size = strtoul (size_str, &size_str_after, 10); - if (size_str_after == size_str) - size = 25; /* Not a number */ - else if (strcmp (size_str_after, "px") == 0) - size_in_pixels = true; + unsigned long parsed_size; + parsed_size = strtoul (size_str, &size_str_after, 10); + + if (size_str_after != size_str) { + if (strcmp (size_str_after, "px") == 0) { + size_in_pixels = true; + size.as_pixels_unit.pixels = parsed_size; + } else { + size.as_points_unit.points = parsed_size; + } + } } if (size_in_pixels) - FT_Set_Pixel_Sizes (label->face, 0, size); + FT_Set_Pixel_Sizes (label->face, 0, size.as_pixels_unit.pixels * label->scale_factor); else - FT_Set_Char_Size (label->face, size << 6, 0, 72, 0); + FT_Set_Char_Size (label->face, size.as_integer, 0, dpi * label->scale_factor, 0); /* Ignore errors, to keep the current size. */ |