[pango/tab-align: 1/3] layout: Implement tab alignments




commit a9f70c80bec270856bdd51e0113bfab2b2e3b497
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Nov 28 18:40:14 2021 -0500

    layout: Implement tab alignments
    
    Implement the other tab alignments by adjusting
    the tab width as we go.
    
    Based on an old patch by Itai Bar-Haim.
    
    This also includes a fix for the previously
    supported left tab alignment in the presence
    of indent.
    
    Fixes: #34

 pango/pango-layout.c | 243 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 189 insertions(+), 54 deletions(-)
---
diff --git a/pango/pango-layout.c b/pango/pango-layout.c
index ebdea36a..ddff8356 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -3262,6 +3262,7 @@ pango_layout_line_leaked (PangoLayoutLine *line)
  *****************/
 
 static void shape_tab (PangoLayoutLine  *line,
+                       ParaBreakState   *state,
                        PangoItem        *item,
                        PangoGlyphString *glyphs);
 
@@ -3367,54 +3368,54 @@ ensure_tab_width (PangoLayout *layout)
     }
 }
 
-/* For now we only need the tab position, we assume
- * all tabs are left-aligned.
- */
-static int
-get_tab_pos (PangoLayout *layout,
-             int          index,
-             gboolean    *is_default)
+static void
+get_tab_pos (PangoLayoutLine *line,
+             int              index,
+             int             *tab_pos,
+             PangoTabAlign   *alignment,
+             gboolean        *is_default)
 {
-  gint n_tabs;
+  PangoLayout *layout = line->layout;
+  int n_tabs;
   gboolean in_pixels;
+  int offset = 0;
+
+  if (layout->alignment != PANGO_ALIGN_CENTER)
+    {
+      if (line->is_paragraph_start && layout->indent >= 0)
+        offset = layout->indent;
+      else if (!line->is_paragraph_start && layout->indent < 0)
+        offset = - layout->indent;
+    }
 
   if (layout->tabs)
     {
       n_tabs = pango_tab_array_get_size (layout->tabs);
       in_pixels = pango_tab_array_get_positions_in_pixels (layout->tabs);
-      if (is_default)
-        *is_default = FALSE;
+      *is_default = FALSE;
     }
   else
     {
       n_tabs = 0;
       in_pixels = FALSE;
-      if (is_default)
-        *is_default = TRUE;
+      *is_default = TRUE;
     }
 
   if (index < n_tabs)
     {
-      gint pos = 0;
-
-      pango_tab_array_get_tab (layout->tabs, index, NULL, &pos);
+      pango_tab_array_get_tab (layout->tabs, index, alignment, tab_pos);
 
       if (in_pixels)
-        return pos * PANGO_SCALE;
-      else
-        return pos;
+        *tab_pos *= PANGO_SCALE;
     }
-
-  if (n_tabs > 0)
+  else if (n_tabs > 0)
     {
-      /* Extrapolate tab position, repeating the last tab gap to
-       * infinity.
-       */
+      /* Extrapolate tab position, repeating the last tab gap to infinity. */
       int last_pos = 0;
       int next_to_last_pos = 0;
       int tab_width;
 
-      pango_tab_array_get_tab (layout->tabs, n_tabs - 1, NULL, &last_pos);
+      pango_tab_array_get_tab (layout->tabs, n_tabs - 1, alignment, &last_pos);
 
       if (n_tabs > 1)
         pango_tab_array_get_tab (layout->tabs, n_tabs - 2, NULL, &next_to_last_pos);
@@ -3428,22 +3429,19 @@ get_tab_pos (PangoLayout *layout,
         }
 
       if (last_pos > next_to_last_pos)
-        {
-          tab_width = last_pos - next_to_last_pos;
-        }
+        tab_width = last_pos - next_to_last_pos;
       else
-        {
-          tab_width = layout->tab_width;
-        }
+        tab_width = layout->tab_width;
 
-      return last_pos + tab_width * (index - n_tabs + 1);
+      *tab_pos = last_pos + tab_width * (index - n_tabs + 1);
     }
   else
     {
-      /* No tab array set, so use default tab width
-       */
-      return layout->tab_width * index;
+      /* No tab array set, so use default tab width */
+      *tab_pos = layout->tab_width * index;
     }
+
+  *tab_pos -= offset;
 }
 
 static int
@@ -3460,7 +3458,7 @@ line_width (PangoLayoutLine *line)
     {
       PangoLayoutRun *run = l->data;
 
-      for (i=0; i < run->glyphs->num_glyphs; i++)
+      for (i = 0; i < run->glyphs->num_glyphs; i++)
         width += run->glyphs->glyphs[i].geometry.width;
     }
 
@@ -3484,14 +3482,24 @@ showing_space (const PangoAnalysis *analysis)
   return FALSE;
 }
 
+static void break_state_set_last_tab (ParaBreakState   *state,
+                                      PangoGlyphString *glyphs,
+                                      int               width,
+                                      int               tab_pos,
+                                      PangoTabAlign     tab_align);
+
 static void
 shape_tab (PangoLayoutLine  *line,
+           ParaBreakState   *state,
            PangoItem        *item,
            PangoGlyphString *glyphs)
 {
   int i, space_width;
+  int current_width;
+  int tab_pos;
+  PangoTabAlign tab_align;
 
-  int current_width = line_width (line);
+  current_width = line_width (line);
 
   pango_glyph_string_set_size (glyphs, 1);
 
@@ -3499,6 +3507,7 @@ shape_tab (PangoLayoutLine  *line,
     glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH ('\t');
   else
     glyphs->glyphs[0].glyph = PANGO_GLYPH_EMPTY;
+
   glyphs->glyphs[0].geometry.x_offset = 0;
   glyphs->glyphs[0].geometry.y_offset = 0;
   glyphs->glyphs[0].attr.is_cluster_start = 1;
@@ -3509,22 +3518,28 @@ shape_tab (PangoLayoutLine  *line,
   ensure_tab_width (line->layout);
   space_width = line->layout->tab_width / 8;
 
-  for (i=0;;i++)
+  for (i = 0; ; i++)
     {
       gboolean is_default;
-      int tab_pos = get_tab_pos (line->layout, i, &is_default);
+
+      get_tab_pos (line, i, &tab_pos, &tab_align, &is_default);
+
       /* Make sure there is at least a space-width of space between
-       * tab-aligned text and the text before it.  However, only do
+       * tab-aligned text and the text before it. However, only do
        * this if no tab array is set on the layout, ie. using default
-       * tab positions.  If use has set tab positions, respect it to
-       * the pixel.
+       * tab positions. If the user has set tab positions, respect it
+       * to the pixel.
        */
       if (tab_pos >= current_width + (is_default ? space_width : 1))
         {
           glyphs->glyphs[0].geometry.width = tab_pos - current_width;
+          g_debug ("shape_tab: tab %d, align %d, width %d\n",
+                   i, tab_align, glyphs->glyphs[0].geometry.width);
           break;
         }
     }
+
+  break_state_set_last_tab (state, glyphs, current_width, tab_pos, tab_align);
 }
 
 static inline gboolean
@@ -3608,12 +3623,65 @@ struct _ParaBreakState
   int hyphen_width;             /* How much space a hyphen will take */
 
   GList *baseline_shifts;
+
+  PangoGlyphString *last_tab;
+  int last_tab_width;
+  int last_tab_pos;
+  PangoTabAlign last_tab_align;
 };
 
+static void
+break_state_set_last_tab (ParaBreakState   *state,
+                          PangoGlyphString *glyphs,
+                          int               width,
+                          int               tab_pos,
+                          PangoTabAlign     tab_align)
+{
+
+  state->last_tab = glyphs;
+  state->last_tab_width = width;
+  state->last_tab_pos = tab_pos;
+  state->last_tab_align = tab_align;
+}
+
 static gboolean
 should_ellipsize_current_line (PangoLayout    *layout,
                                ParaBreakState *state);
 
+static void
+get_decimal_prefix_width (PangoItem        *item,
+                          PangoGlyphString *glyphs,
+                          const char       *text,
+                          int              *width,
+                          gboolean         *found)
+{
+  PangoGlyphItem glyph_item = { item, glyphs, 0, 0, 0 };
+  int *log_widths;
+  int i;
+  const char *p;
+
+  log_widths = g_new (int, item->num_chars);
+
+  pango_glyph_item_get_logical_widths (&glyph_item, text, log_widths);
+
+  *width = 0;
+  *found = FALSE;
+
+  for (i = 0, p = text + item->offset; i < item->num_chars; i++, p = g_utf8_next_char (p))
+    {
+      if (g_utf8_get_char (p) == '.')
+        {
+          *width += log_widths[i] / 2;
+          *found = TRUE;
+          break;
+        }
+
+      *width += log_widths[i];
+    }
+
+  g_free (log_widths);
+}
+
 static PangoGlyphString *
 shape_run (PangoLayoutLine *line,
            ParaBreakState  *state,
@@ -3623,7 +3691,7 @@ shape_run (PangoLayoutLine *line,
   PangoGlyphString *glyphs = pango_glyph_string_new ();
 
   if (layout->text[item->offset] == '\t')
-    shape_tab (line, item, glyphs);
+    shape_tab (line, state, item, glyphs);
   else
     {
       PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
@@ -3661,6 +3729,31 @@ shape_run (PangoLayoutLine *line,
           glyphs->glyphs[0].geometry.x_offset += space_left;
           glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += space_right;
         }
+
+      if (state->last_tab != NULL)
+        {
+          int w;
+
+          g_assert (state->last_tab->num_glyphs == 1);
+
+          w = state->last_tab_pos - state->last_tab_width;
+
+          if (state->last_tab_align == PANGO_TAB_RIGHT)
+            w -= pango_glyph_string_get_width (glyphs);
+          else if (state->last_tab_align == PANGO_TAB_CENTER)
+            w -= pango_glyph_string_get_width (glyphs) / 2;
+          else if (state->last_tab_align == PANGO_TAB_DECIMAL)
+            {
+              int width;
+              gboolean found;
+
+              get_decimal_prefix_width (item, glyphs, layout->text, &width, &found);
+
+              w -= width;
+            }
+
+          state->last_tab->glyphs[0].geometry.width = MAX (w, 0);
+        }
     }
 
   return glyphs;
@@ -3694,6 +3787,25 @@ insert_run (PangoLayoutLine  *line,
 
   line->runs = g_slist_prepend (line->runs, run);
   line->length += run_item->length;
+
+  if (state->last_tab && run->glyphs != state->last_tab)
+    {
+      if (state->last_tab_align == PANGO_TAB_RIGHT)
+        state->last_tab_width += pango_glyph_string_get_width (run->glyphs);
+      else if (state->last_tab_align == PANGO_TAB_CENTER)
+        state->last_tab_width += pango_glyph_string_get_width (run->glyphs) / 2;
+      else if (state->last_tab_align == PANGO_TAB_DECIMAL)
+        {
+          int width;
+          gboolean found;
+
+          get_decimal_prefix_width (run->item, run->glyphs, line->layout->text, &width, &found);
+
+          state->last_tab_width += width;
+          if (found)
+            state->last_tab = NULL;
+        }
+    }
 }
 
 static gboolean
@@ -3765,7 +3877,7 @@ find_break_extra_width (PangoLayout    *layout,
   return 0;
 }
 
-#if 0
+#if 1
 # define DEBUG debug
 static int pango_layout_line_get_width (PangoLayoutLine *line);
 static void
@@ -3801,6 +3913,15 @@ compute_log_widths (PangoLayout    *layout,
   pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
 }
 
+static int
+tab_width_change (ParaBreakState *state)
+{
+  if (state->last_tab)
+    return state->last_tab->glyphs[0].geometry.width - (state->last_tab_pos - state->last_tab_width);
+
+  return 0;
+}
+
 /* Tries to insert as much as possible of the item at the head of
  * state->items onto @line. Five results are possible:
  *
@@ -3916,7 +4037,6 @@ process_item (PangoLayout     *layout,
   if (state->remaining_width < 0 && !no_break_at_end)  /* Wrapping off */
     {
       insert_run (line, state, item, NULL, TRUE);
-
       DEBUG1 ("no wrapping, all-fit");
       return BREAK_ALL_FIT;
     }
@@ -3932,6 +4052,16 @@ process_item (PangoLayout     *layout,
         width += state->log_widths[state->log_widths_offset + i];
     }
 
+  if (layout->text[item->offset] == '\t')
+    {
+      insert_run (line, state, item, NULL, TRUE);
+      state->remaining_width -= width;
+      state->remaining_width = MAX (state->remaining_width, 0);
+
+      DEBUG1 ("tab run, all-fit");
+      return BREAK_ALL_FIT;
+    }
+
   if (!no_break_at_end &&
       can_break_at (layout, state->start_offset + item->num_chars, wrap))
     {
@@ -3946,31 +4076,32 @@ process_item (PangoLayout     *layout,
   else
     extra_width = 0;
 
-  if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) &&
+  if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs) ||
+      (state->last_tab && state->last_tab_align != PANGO_TAB_LEFT)) &&
       !no_break_at_end)
     {
+      PangoGlyphString *glyphs;
+
       DEBUG1 ("%d + %d <= %d", width, extra_width, state->remaining_width);
-      insert_run (line, state, item, NULL, FALSE);
+      glyphs = shape_run (line, state, item);
 
-      width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
+      width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
 
       if (width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs))
         {
+          insert_run (line, state, item, glyphs, TRUE);
+
           state->remaining_width -= width;
           state->remaining_width = MAX (state->remaining_width, 0);
 
-          /* We passed last_run == FALSE to insert_run, so it did not do this */
-          pango_glyph_string_free (state->glyphs);
-          state->glyphs = NULL;
-
           DEBUG1 ("early accept '%.*s', all-fit, remaining %d",
                   item->length, layout->text + item->offset,
                   state->remaining_width);
           return BREAK_ALL_FIT;
         }
 
-      /* if it doesn't fit after shaping, revert and proceed to break the item */
-      uninsert_run (line);
+      /* if it doesn't fit after shaping, discard and proceed to break the item */
+      pango_glyph_string_free (glyphs);
     }
 
   /*** From here on, we look for a way to break item ***/
@@ -4055,7 +4186,7 @@ retry_break:
 
               glyphs = shape_run (line, state, new_item);
 
-              new_break_width = pango_glyph_string_get_width (glyphs);
+              new_break_width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
 
               if (num_chars > 0 &&
                   layout->log_attrs[state->start_offset + num_chars - 1].is_white)
@@ -4316,6 +4447,10 @@ process_line (PangoLayout    *layout,
     state->remaining_width = -1;
   else
     state->remaining_width = state->line_width;
+
+  state->last_tab = NULL;
+  state->last_tab_align = PANGO_TAB_LEFT;
+
   DEBUG ("starting to fill line", line, state);
 
   while (state->items)


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