[gtk/dotted-lines2: 1/2] Implement dashed and dotted underlines




commit 14b1873e5ac4352dd99885872d0848d00d8d8c7f
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Mar 30 23:59:38 2021 -0400

    Implement dashed and dotted underlines
    
    This is implementing rendering dashed and dotted lines, using
    a private attribute that can be added to PangoItems after layout.
    
    This is currently using a linear gradient for dashes and a repeat
    node for dots.

 gtk/gskpango.c             | 115 ++++++++++++++++++++++++++++++++++++++++-----
 gtk/gskpango.h             |   3 ++
 gtk/gtklabel.c             |  81 +++++++++++++++++++++++++++++++
 gtk/gtktextlayout.c        |  45 ++++++++++++++++++
 gtk/gtktextlayoutprivate.h |  15 ++++++
 5 files changed, 247 insertions(+), 12 deletions(-)
---
diff --git a/gtk/gskpango.c b/gtk/gskpango.c
index cfe868cb7a..ad9dd00a3b 100644
--- a/gtk/gskpango.c
+++ b/gtk/gskpango.c
@@ -108,23 +108,88 @@ gsk_pango_renderer_draw_glyph_item (PangoRenderer  *renderer,
 }
 
 static void
-gsk_pango_renderer_draw_rectangle (PangoRenderer     *renderer,
-                                   PangoRenderPart    part,
-                                   int                x,
-                                   int                y,
-                                   int                width,
-                                   int                height)
+gsk_pango_renderer_draw_rectangle (PangoRenderer   *renderer,
+                                   PangoRenderPart  part,
+                                   int              x,
+                                   int              y,
+                                   int              width,
+                                   int              height)
 {
   GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
   GdkRGBA rgba;
+  double xx, yy, ww, hh;
+  GtkLineStyle line_style;
+
+  xx = (double)x / PANGO_SCALE;
+  yy = (double)y / PANGO_SCALE;
+  ww = (double)width / PANGO_SCALE;
+  hh = (double)height / PANGO_SCALE;
 
   get_color (crenderer, part, &rgba);
-  gtk_snapshot_append_color (crenderer->snapshot,
-                             &rgba,
-                             &GRAPHENE_RECT_INIT ((double)x / PANGO_SCALE,
-                                                  (double)y / PANGO_SCALE,
-                                                  (double)width / PANGO_SCALE,
-                                                  (double)height / PANGO_SCALE));
+
+  if (part == PANGO_RENDER_PART_UNDERLINE ||
+      part == PANGO_RENDER_PART_STRIKETHROUGH ||
+      part == PANGO_RENDER_PART_OVERLINE)
+    line_style = crenderer->line_style;
+  else
+    line_style = GTK_LINE_STYLE_SOLID;
+
+  switch (line_style)
+    {
+    case GTK_LINE_STYLE_SOLID:
+      gtk_snapshot_append_color (crenderer->snapshot,
+                                 &rgba,
+                                 &GRAPHENE_RECT_INIT (xx, yy, ww, hh));
+      break;
+
+    case GTK_LINE_STYLE_DOTTED:
+      {
+        GskRoundedRect dot;
+        double d = hh;
+        graphene_rect_t bounds = GRAPHENE_RECT_INIT (xx, yy, d, d);
+        graphene_size_t rr = GRAPHENE_SIZE_INIT (d/2, d/2);
+        GdkRGBA transparent = { 0.f, 0.f, 0.f, 0.f };
+
+        gsk_rounded_rect_init (&dot, &bounds, &rr, &rr, &rr, &rr);
+
+        gtk_snapshot_push_repeat (crenderer->snapshot,
+                                  &GRAPHENE_RECT_INIT (xx, yy, ww, hh),
+                                  NULL);
+
+        gtk_snapshot_push_rounded_clip (crenderer->snapshot, &dot);
+        gtk_snapshot_append_color (crenderer->snapshot, &rgba, &bounds);
+        gtk_snapshot_pop (crenderer->snapshot);
+        gtk_snapshot_append_color (crenderer->snapshot, &transparent, &GRAPHENE_RECT_INIT (xx + d, yy, 0.5 * 
d, d));
+        gtk_snapshot_pop (crenderer->snapshot);
+      }
+      break;
+
+    case GTK_LINE_STYLE_DASHED:
+      {
+        GskColorStop stops[4];
+        GdkRGBA transparent = { 0.f, 0.f, 0.f, 0.f };
+
+        stops[0].offset = 0;
+        stops[0].color = rgba;
+        stops[1].offset = 0.66;
+        stops[1].color = rgba;
+        stops[2].offset = 0.66;
+        stops[2].color = transparent;
+        stops[3].offset = 1;
+        stops[3].color = transparent;
+
+        gtk_snapshot_append_repeating_linear_gradient (
+                                  crenderer->snapshot,
+                                  &GRAPHENE_RECT_INIT (xx, yy, ww, hh),
+                                  &GRAPHENE_POINT_INIT (xx, yy),
+                                  &GRAPHENE_POINT_INIT (xx + 9 * MIN (ww, hh), yy),
+                                  stops, 4);
+      }
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
 }
 
 static void
@@ -343,6 +408,24 @@ get_item_appearance (PangoItem *item)
   return NULL;
 }
 
+static GtkLineStyle
+get_item_line_style (PangoItem *item)
+{
+  GSList *tmp_list = item->analysis.extra_attrs;
+
+  while (tmp_list)
+    {
+      PangoAttribute *attr = tmp_list->data;
+
+      if (attr->klass->type == gtk_text_attr_line_style_type)
+        return ((GtkTextAttrLineStyle *)attr)->value;
+
+      tmp_list = tmp_list->next;
+    }
+
+  return GTK_LINE_STYLE_SOLID;
+}
+
 static void
 gsk_pango_renderer_prepare_run (PangoRenderer  *renderer,
                                 PangoLayoutRun *run)
@@ -351,9 +434,17 @@ gsk_pango_renderer_prepare_run (PangoRenderer  *renderer,
   const GdkRGBA *bg_rgba = NULL;
   const GdkRGBA *fg_rgba = NULL;
   GtkTextAppearance *appearance;
+  GtkLineStyle line_style;
 
   PANGO_RENDERER_CLASS (gsk_pango_renderer_parent_class)->prepare_run (renderer, run);
 
+  line_style = get_item_line_style (run->item);
+  if (crenderer->line_style != line_style)
+    {
+      pango_renderer_part_changed (renderer, PANGO_RENDER_PART_UNDERLINE);
+      crenderer->line_style = get_item_line_style (run->item);
+    }
+
   appearance = get_item_appearance (run->item);
 
   if (appearance == NULL)
diff --git a/gtk/gskpango.h b/gtk/gskpango.h
index 672128d1c1..8d718cfe20 100644
--- a/gtk/gskpango.h
+++ b/gtk/gskpango.h
@@ -21,6 +21,7 @@
 
 #include <pango/pango.h>
 #include "gtk/gtksnapshot.h"
+#include "gtk/gtktextlayoutprivate.h"
 
 G_BEGIN_DECLS
 
@@ -68,6 +69,8 @@ struct _GskPangoRenderer
   guint                  is_cached_renderer : 1;
 
   GskPangoShapeHandler   shape_handler;
+
+  GtkLineStyle line_style;
 };
 
 struct _GskPangoRendererClass
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c
index 10d65667e9..84ee2c0f75 100644
--- a/gtk/gtklabel.c
+++ b/gtk/gtklabel.c
@@ -52,6 +52,7 @@
 #include "gtkdragsourceprivate.h"
 #include "gtkdragicon.h"
 #include "gtkcsscolorvalueprivate.h"
+#include "gtktextlayoutprivate.h"
 
 #include <math.h>
 #include <string.h>
@@ -1259,6 +1260,85 @@ gtk_label_size_allocate (GtkWidget *widget,
 }
 
 
+static void
+add_dotted_underline (PangoLayout *layout)
+{
+  const char *text;
+  char *p;
+  GSList *l, *ll, *a;
+
+  text = pango_layout_get_text (layout);
+  p = strstr (text, "good");
+
+  for (int i = 0; i < pango_layout_get_line_count (layout); i++)
+    {
+      PangoLayoutLine *line = pango_layout_get_line (layout, i);
+
+      for (l = line->runs, ll = NULL; l; ll = l, l = l->next)
+        {
+          PangoGlyphItem *run = l->data;
+
+          if (run == NULL)
+            continue;
+
+          if (p != NULL && text + run->item->offset <= p && p + strlen ("good") <= text + run->item->offset 
+ run->item->length)
+            {
+               /* Match inside this item */
+               if (text + run->item->offset < p)
+                 {
+                   PangoGlyphItem *before;
+
+                   /* split off initial segment */
+                   before = pango_glyph_item_split (run, text, p - (text + run->item->offset));
+                   if (ll)
+                     {
+                       ll->next = g_slist_prepend (l, before);
+                       ll = ll->next;
+                     }
+                   else
+                     {
+                       line->runs = g_slist_prepend (line->runs, before);
+                       ll = line->runs;
+                     }
+                 }
+
+               if (p + strlen ("good") < text + run->item->offset + run->item->length)
+                 {
+                   /* split off final segment */
+                   run = pango_glyph_item_split (run, text, strlen ("good"));
+
+                   if (ll)
+                     ll->next = g_slist_prepend (l, run);
+                   else
+                     line->runs = g_slist_prepend (line->runs, run);
+                 }
+
+               for (a = run->item->analysis.extra_attrs; a; a = a->next)
+                 {
+                   PangoAttribute *attr = a->data;
+
+                   if (attr->klass->type == gtk_text_attr_line_style_type)
+                     break;
+                 }
+
+               if (a == NULL)
+                 {
+                   PangoAttribute *attr;
+
+                   attr = gtk_text_attr_line_style_new (GTK_LINE_STYLE_DOTTED);
+                   run->item->analysis.extra_attrs =
+                       g_slist_prepend (run->item->analysis.extra_attrs, attr);
+
+                   attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+                   run->item->analysis.extra_attrs =
+                       g_slist_prepend (run->item->analysis.extra_attrs, attr);
+                 }
+
+               return;
+            }
+        }
+    }
+}
 
 #define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height))
 
@@ -1280,6 +1360,7 @@ gtk_label_snapshot (GtkWidget   *widget,
   context = _gtk_widget_get_style_context (widget);
   get_layout_location (self, &lx, &ly);
 
+  add_dotted_underline (self->layout);
   gtk_snapshot_render_layout (snapshot, context, lx, ly, self->layout);
 
   info = self->select_info;
diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c
index 05d9fa9b2a..02d89547ea 100644
--- a/gtk/gtktextlayout.c
+++ b/gtk/gtktextlayout.c
@@ -1512,6 +1512,51 @@ gtk_text_attr_appearance_new (const GtkTextAppearance *appearance)
   return (PangoAttribute *)result;
 }
 
+static PangoAttribute *
+attr_line_style_copy (const PangoAttribute *attr)
+{
+  const GtkTextAttrLineStyle *a = (GtkTextAttrLineStyle *)attr;
+  return gtk_text_attr_line_style_new (a->value);
+}
+
+static void
+attr_line_style_destroy (PangoAttribute *attr)
+{
+  g_slice_free (GtkTextAttrLineStyle, (gpointer)attr);
+}
+
+static gboolean
+attr_line_style_equal (const PangoAttribute *attr1,
+                       const PangoAttribute *attr2)
+{
+  const GtkTextAttrLineStyle *a1 = (GtkTextAttrLineStyle *)attr1;
+  const GtkTextAttrLineStyle *a2 = (GtkTextAttrLineStyle *)attr2;
+  return a1->value == a2->value;
+}
+
+PangoAttrType gtk_text_attr_line_style_type = 0;
+
+PangoAttribute *
+gtk_text_attr_line_style_new (GtkLineStyle style)
+{
+  static PangoAttrClass klass = {
+    0,
+    attr_line_style_copy,
+    attr_line_style_destroy,
+    attr_line_style_equal
+  };
+  GtkTextAttrLineStyle *attr;
+
+  if (!klass.type)
+    klass.type = gtk_text_attr_line_style_type =
+      pango_attr_type_register (I_("GtkTextAttrLineStyle"));
+
+  attr = g_slice_new (GtkTextAttrLineStyle);
+  pango_attribute_init (&attr->attr, &klass);
+  attr->value = style;
+  return (PangoAttribute *)attr;
+}
+
 static void
 add_generic_attrs (GtkTextLayout      *layout,
                    GtkTextAppearance  *appearance,
diff --git a/gtk/gtktextlayoutprivate.h b/gtk/gtktextlayoutprivate.h
index c1a8ed92a5..4824cd7a35 100644
--- a/gtk/gtktextlayoutprivate.h
+++ b/gtk/gtktextlayoutprivate.h
@@ -219,6 +219,21 @@ struct _GtkTextLineDisplay
 
 #ifdef GTK_COMPILATION
 extern G_GNUC_INTERNAL PangoAttrType gtk_text_attr_appearance_type;
+
+typedef enum {
+  GTK_LINE_STYLE_SOLID,
+  GTK_LINE_STYLE_DASHED,
+  GTK_LINE_STYLE_DOTTED
+} GtkLineStyle;
+
+typedef struct {
+  PangoAttribute attr;
+  GtkLineStyle value;
+} GtkTextAttrLineStyle;
+
+extern PangoAttrType gtk_text_attr_line_style_type;
+
+PangoAttribute *gtk_text_attr_line_style_new (GtkLineStyle style);
 #endif
 
 GType         gtk_text_layout_get_type    (void) G_GNUC_CONST;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]