summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/controls/label-freetype/plugin.c485
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,
- &current_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,
&current_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, &current_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. */