]> git.ipfire.org Git - thirdparty/plymouth.git/commitdiff
label-freetype: Add basic support for rich text
authorRay Strode <rstrode@redhat.com>
Sat, 15 Jul 2023 00:47:55 +0000 (20:47 -0400)
committerRay Strode <rstrode@redhat.com>
Sat, 2 Dec 2023 23:19:33 +0000 (18:19 -0500)
Now that the ply-label interface supports rich text, we need to
implement it in the plugins.

This commit changes the freetype plugin to process
rich text in a rudimentary way that picks up colors, but not other
style attributes.

Based on initial work from nerdopolis

src/plugins/controls/label-freetype/plugin.c

index 4744f335c1c039a9b88412c9ecf6c60354b5b27c..aef34f8c7de8bbd0197c6f23972f10865a8d6dca 100644 (file)
@@ -27,6 +27,7 @@
 #include <stdint.h>
 #include <string.h>
 #include <limits.h>
+#include <wchar.h>
 
 #include <ft2build.h>
 #include FT_FREETYPE_H
@@ -53,6 +54,8 @@ struct _ply_label_plugin_control
         FT_Face               face;
 
         char                 *text;
+        ply_rich_text_t      *rich_text;
+        ply_rich_text_span_t  span;
         float                 red;
         float                 green;
         float                 blue;
@@ -205,8 +208,8 @@ load_character (ply_label_plugin_control_t *label,
 }
 
 static FT_Int
-width_of_line (ply_label_plugin_control_t *label,
-               const char                 *text)
+width_of_string (ply_label_plugin_control_t *label,
+                 const char                 *text)
 {
         FT_Int width = 0;
         FT_Int left_bearing = 0;
@@ -228,26 +231,65 @@ width_of_line (ply_label_plugin_control_t *label,
 static void
 size_control (ply_label_plugin_control_t *label)
 {
-        FT_Int width;
-        const char *text = label->text;
+        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_init (&rich_text_iterator,
+                                             label->rich_text,
+                                             &label->span);
+        } else {
+                ply_utf8_string_iterator_init (&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 */
-        while (text && *text) {
-                width = width_of_line (label, text);
-                if ((uint32_t) width > label->area.width)
-                        label->area.width = width;
-
-                label->area.height += (label->face->size->metrics.ascender
-                                       - label->face->size->metrics.descender) >> 6;
-
-                text = strchr (text, '\n');
-                /* skip newline character */
-                if (text)
-                        ++text;
-        }
+        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)
@@ -277,7 +319,10 @@ draw_bitmap (ply_label_plugin_control_t *label,
              ply_rectangle_t             target_size,
              FT_Bitmap                  *source,
              FT_Int                      x_start,
-             FT_Int                      y_start)
+             FT_Int                      y_start,
+             uint8_t                     rs,
+             uint8_t                     gs,
+             uint8_t                     bs)
 {
         FT_Int x, y, xs, ys;
         FT_Int x_end = MIN (x_start + source->width, target_size.width);
@@ -287,10 +332,7 @@ draw_bitmap (ply_label_plugin_control_t *label,
             (uint32_t) y_start >= target_size.height)
                 return;
 
-        uint8_t rs, gs, bs, rd, gd, bd, ad;
-        rs = 255 * label->red;
-        gs = 255 * label->green;
-        bs = 255 * label->blue;
+        uint8_t rd, gd, bd, ad;
 
         for (y = y_start, ys = 0; y < y_end; ++y, ++ys) {
                 for (x = x_start, xs = 0; x < x_end; ++x, ++xs) {
@@ -317,6 +359,67 @@ draw_bitmap (ply_label_plugin_control_t *label,
         }
 }
 
+static void
+look_up_rgb_color_from_terminal_color (ply_label_plugin_control_t *label,
+                                       ply_terminal_color_t        color,
+                                       uint8_t                    *red,
+                                       uint8_t                    *green,
+                                       uint8_t                    *blue)
+{
+        switch (color) {
+        case PLY_TERMINAL_COLOR_BLACK:
+                *red = 0x00;
+                *green = 0x00;
+                *blue = 0x00;
+                break;
+        /* Linux VT Color: 0xaa0000 */
+        case PLY_TERMINAL_COLOR_RED:
+                *red = 0xaa;
+                *green = 0x00;
+                *blue = 0x00;
+                break;
+        /* Linux VT Color: 0x00aa00 */
+        case PLY_TERMINAL_COLOR_GREEN:
+                *red = 0x00;
+                *green = 0xaa;
+                *blue = 0x00;
+                break;
+        /* Linux VT Color: 0xaa5500 */
+        case PLY_TERMINAL_COLOR_BROWN:
+                *red = 0xaa;
+                *green = 0x55;
+                *blue = 0x00;
+                break;
+        /* Linux VT Color: 0x0000aa */
+        case PLY_TERMINAL_COLOR_BLUE:
+                *red = 0x00;
+                *green = 0x00;
+                *blue = 0xaa;
+                break;
+        /* Linux VT Color: 0xaa00aa */
+        case PLY_TERMINAL_COLOR_MAGENTA:
+                *red = 0xaa;
+                *green = 0x00;
+                *blue = 0xaa;
+                break;
+        /* Linux VT Color: 0x00aaaa */
+        case PLY_TERMINAL_COLOR_CYAN:
+                *red = 0x00;
+                *green = 0xaa;
+                *blue = 0xaa;
+                break;
+        /* Linux VT Color: 0xaaaaaa */
+        case PLY_TERMINAL_COLOR_WHITE:
+                break;
+
+        default:
+                *red = 255 * label->red;
+                *green = 255 * label->green;
+                *blue = 255 * label->blue;
+                break;
+        }
+}
+
 static void
 draw_control (ply_label_plugin_control_t *label,
               ply_pixel_buffer_t         *pixel_buffer,
@@ -325,16 +428,19 @@ draw_control (ply_label_plugin_control_t *label,
               unsigned long               width,
               unsigned long               height)
 {
-        FT_Error error;
         FT_Vector pen;
         FT_GlyphSlot slot;
-        const char *cur_c;
+        ply_rich_text_iterator_t rich_text_iterator;
+        ply_utf8_string_iterator_t utf8_string_iterator;
         uint32_t *target;
         ply_rectangle_t target_size;
-
         if (label->is_hidden)
                 return;
 
+        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
@@ -344,7 +450,16 @@ draw_control (ply_label_plugin_control_t *label,
 
         slot = label->face->glyph;
 
-        cur_c = label->text;
+        if (label->rich_text != NULL) {
+                ply_rich_text_iterator_init (&rich_text_iterator,
+                                             label->rich_text,
+                                             &label->span);
+        } else {
+                ply_utf8_string_iterator_init (&utf8_string_iterator,
+                                               label->text,
+                                               0,
+                                               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);
@@ -359,46 +474,77 @@ draw_control (ply_label_plugin_control_t *label,
         pen.y += label->face->size->metrics.ascender;
 
         /* Go through each line */
-        while (*cur_c) {
+        do {
+                bool should_stop;
+                const char *current_character;
+                uint8_t red, green, blue;
+
+                FT_Int extraAdvance = 0, positiveBearingX = 0;
+                ply_rich_text_character_t *rich_text_character;
+
+                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,
+                                                                    &rich_text_character);
+                        if (should_stop)
+                                break;
+
+                        current_character = rich_text_character->bytes;
+
+                        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 (*current_character == '\n')
+                        continue;
+
                 pen.x = label->area.x << 6;
 
                 /* Start at start position (alignment) */
                 if (label->alignment == PLY_LABEL_ALIGN_CENTER)
-                        pen.x += (label->area.width - width_of_line (label, cur_c)) << 5;
+                        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_line (label, cur_c)) << 6;
-
-                while (*cur_c && *cur_c != '\n') {
-                        FT_Int extraAdvance = 0, positiveBearingX = 0;
-
-                        if (!load_character (label, &cur_c))
-                                continue;
-
-                        /* We consider negative left bearing an increment in size,
-                         * as we draw full character boxes and don't "go back" in
-                         * this plugin. Positive left bearing is treated as usual.
-                         * For definitions see
-                         * https://freetype.org/freetype2/docs/glyphs/glyphs-3.html
-                         */
-                        if (slot->bitmap_left < 0) {
-                                extraAdvance = -slot->bitmap_left;
-                        } else {
-                                positiveBearingX = slot->bitmap_left;
-                        }
-                        draw_bitmap (label, target, target_size, &slot->bitmap,
-                                     (pen.x >> 6) + positiveBearingX,
-                                     (pen.y >> 6) - slot->bitmap_top);
-
-                        pen.x += slot->advance.x + extraAdvance;
-                        pen.y += slot->advance.y;
-                }
-                /* skip newline character */
-                if (*cur_c)
-                        ++cur_c;
+                        pen.x += (label->area.width - width_of_string (label, current_character)) << 6;
+
+                if (!load_character (label, &current_character))
+                        continue;
+
+                /* We consider negative left bearing an increment in size,
+                 * as we draw full character boxes and don't "go back" in
+                 * this plugin. Positive left bearing is treated as usual.
+                 * For definitions see
+                 * https://freetype.org/freetype2/docs/glyphs/glyphs-3.html
+                 */
+                if (slot->bitmap_left < 0)
+                        extraAdvance = -slot->bitmap_left;
+                else
+                        positiveBearingX = slot->bitmap_left;
+
+                draw_bitmap (label, target, target_size, &slot->bitmap,
+                             (pen.x >> 6) + positiveBearingX,
+                             (pen.y >> 6) - slot->bitmap_top,
+                             red,
+                             green,
+                             blue);
+
+                pen.x += slot->advance.x + extraAdvance;
+                pen.y += slot->advance.y;
 
                 /* Next line */
                 pen.y += label->face->size->metrics.height;
-        }
+        } while (true);
 }
 
 static void
@@ -421,17 +567,45 @@ set_width_for_control (ply_label_plugin_control_t *label,
         }
 }
 
+static void
+clear_text (ply_label_plugin_control_t *label)
+{
+        free (label->text);
+        label->text = NULL;
+
+        if (label->rich_text != NULL) {
+                ply_rich_text_drop_reference (label->rich_text);
+                label->rich_text = NULL;
+                label->span.offset = 0;
+                label->span.range = 0;
+        }
+}
+
 static void
 set_text_for_control (ply_label_plugin_control_t *label,
                       const char                 *text)
 {
         if (label->text != text) {
-                free (label->text);
+                clear_text (label);
                 label->text = strdup (text);
                 trigger_redraw (label, true);
         }
 }
 
+static void
+set_rich_text_for_control (ply_label_plugin_control_t *label,
+                           ply_rich_text_t            *rich_text,
+                           ply_rich_text_span_t       *span)
+{
+        clear_text (label);
+
+        label->rich_text = rich_text;
+        ply_rich_text_take_reference (rich_text);
+        label->span = *span;
+
+        trigger_redraw (label, true);
+}
+
 static void
 set_font_for_control (ply_label_plugin_control_t *label,
                       const char                 *fontdesc)
@@ -557,6 +731,7 @@ ply_label_plugin_get_interface (void)
                 .draw_control              = draw_control,
                 .is_control_hidden         = is_control_hidden,
                 .set_text_for_control      = set_text_for_control,
+                .set_rich_text_for_control = set_rich_text_for_control,
                 .set_alignment_for_control = set_alignment_for_control,
                 .set_width_for_control     = set_width_for_control,
                 .set_font_for_control      = set_font_for_control,