[pango/rewrite-line-breaking] Rewrite process_item




commit d44aa852328ec27dcc0fd045f9d34e88134f2e17
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Nov 15 18:49:54 2021 -0500

    Rewrite process_item
    
    This is not an entire rewrite, the basic approach
    to fitting items is still the same. The main difference
    here is that we shape and measure a possible breakpoint
    before accepting it as candidate for breaking. This
    is necessary to ensure that we have accurate width
    information when making decisions about this
    breakpoint - the width we are calculating based on
    log widths is only an approximation (due to things
    like clusters, hyphens, final spaces, etc).
    
    To avoid excessive shaping, we only do this extra
    work when we are close to the end of the line.

 pango/pango-layout.c | 518 ++++++++++++++++++++++++++++++---------------------
 1 file changed, 304 insertions(+), 214 deletions(-)
---
diff --git a/pango/pango-layout.c b/pango/pango-layout.c
index 471cbb64..1a5f9b0d 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -3526,31 +3526,16 @@ shape_tab (PangoLayoutLine  *line,
 }
 
 static inline gboolean
-can_break_at (PangoLayout *layout,
-              gint         offset,
-              gboolean     always_wrap_char)
+can_break_at (PangoLayout   *layout,
+              gint           offset,
+              PangoWrapMode  wrap)
 {
-  PangoWrapMode wrap;
-  /* We probably should have a mode where we treat all white-space as
-   * of fungible width - appropriate for typography but not for
-   * editing.
-   */
-  wrap = layout->wrap;
-
-  if (wrap == PANGO_WRAP_WORD_CHAR)
-    wrap = always_wrap_char ? PANGO_WRAP_CHAR : PANGO_WRAP_WORD;
-
   if (offset == layout->n_chars)
     return TRUE;
-  else if (wrap == PANGO_WRAP_WORD)
-    return layout->log_attrs[offset].is_line_break;
   else if (wrap == PANGO_WRAP_CHAR)
     return layout->log_attrs[offset].is_char_break;
   else
-    {
-      g_warning (G_STRLOC": broken PangoLayout");
-      return TRUE;
-    }
+    return layout->log_attrs[offset].is_line_break;
 }
 
 static inline gboolean
@@ -3562,7 +3547,7 @@ can_break_in (PangoLayout *layout,
   int i;
 
   for (i = allow_break_at_start ? 0 : 1; i < num_chars; i++)
-    if (can_break_at (layout, start_offset + i, FALSE))
+    if (can_break_at (layout, start_offset + i, layout->wrap))
       return TRUE;
 
   return FALSE;
@@ -3680,17 +3665,20 @@ shape_run (PangoLayoutLine *line,
 }
 
 static void
-insert_run (PangoLayoutLine *line,
-            ParaBreakState  *state,
-            PangoItem       *run_item,
-            gboolean         last_run)
+insert_run (PangoLayoutLine  *line,
+            ParaBreakState   *state,
+            PangoItem        *run_item,
+            PangoGlyphString *glyphs,
+            gboolean          last_run)
 {
   PangoLayoutRun *run = g_slice_new (PangoLayoutRun);
 
   run->item = run_item;
 
-  if (last_run && state->log_widths_offset == 0 &&
-      !(run_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN))
+  if (glyphs)
+    run->glyphs = glyphs;
+  else if (last_run && state->log_widths_offset == 0 &&
+           !(run_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN))
     run->glyphs = state->glyphs;
   else
     run->glyphs = shape_run (line, state, run_item);
@@ -3793,6 +3781,22 @@ debug (const char *where, PangoLayoutLine *line, ParaBreakState *state)
 # define DEBUG(where, line, state) do { } while (0)
 #endif
 
+static inline void
+compute_log_widths (PangoLayout    *layout,
+                    ParaBreakState *state)
+{
+  PangoItem *item = state->items->data;
+  PangoGlyphItem glyph_item = { item, state->glyphs };
+
+  if (item->num_chars > state->num_log_widths)
+    {
+      state->log_widths = g_renew (int, state->log_widths, item->num_chars);
+      state->num_log_widths = item->num_chars;
+    }
+
+  pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
+}
+
 /* Tries to insert as much as possible of the item at the head of
  * state->items onto @line. Five results are possible:
  *
@@ -3811,6 +3815,34 @@ debug (const char *where, PangoLayoutLine *line, ParaBreakState *state)
  * returned even everything fits; the run will be broken earlier,
  * or %BREAK_NONE_FIT returned. This is used when the end of the
  * run is not a break position.
+ *
+ * This function is the core of our line-breaking, and it is long and involved.
+ * Here is an outline of the algorithm, without all the bookkeeping:
+ *
+ * if item appears to fit entirely
+ *   measure it
+ *   if it actually fits
+ *     return BREAK_ALL_FIT
+ *
+ * retry_break:
+ *   for each position p in the item
+ *     if adding more is 'obviously' not going to help and we have a breakpoint
+ *       exit the loop
+ *     if p is a possible break position
+ *       if p is 'obviously' going to fit
+ *         bc = p
+ *       else
+ *         measure breaking at p (taking extra break width into account
+ *         if we don't have a break candidate yet
+ *           bc = p
+ *         else
+ *           if p is better than bc
+ *             bc = p
+ *
+ *   if bc does not fit and we can loosen break conditions
+ *     loosen break conditions and retry break
+ *
+ * return bc
  */
 static BreakResult
 process_item (PangoLayout     *layout,
@@ -3823,29 +3855,55 @@ process_item (PangoLayout     *layout,
   gboolean shape_set = FALSE;
   int width;
   int extra_width;
+  int orig_extra_width;
   int length;
   int i;
-  gboolean processing_new_item = FALSE;
-
-  /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 5.0;
-   * update this if that changes. */
-#define LINE_SEPARATOR 0x2028
-
+  int processing_new_item;
+  int num_chars;
+  int orig_width;
+  PangoWrapMode wrap;
+  int break_num_chars;
+  int break_width;
+  int break_extra_width;
+  PangoGlyphString *break_glyphs;
+  PangoFontMetrics *metrics;
+  int safe_distance;
+
+  g_debug ("process item '%.*s'. Remaining width %d",
+           item->length, layout->text + item->offset,
+           state->remaining_width);
+
+  /* We don't want to shape more than necessary, so we keep the results
+   * of shaping a new item in state->glyphs, state->log_widths. Once
+   * we break off initial parts of the item, we update state->log_widths_offset
+   * to take that into account. Note that the widths we calculate from the
+   * log_widths are an approximation, because a) log_widths are just
+   * evenly divided for clusters, and b) clusters may change as we
+   * break in the middle (think ff- i).
+   *
+   * We use state->log_widths_offset != 0 to detect if we are dealing
+   * with the original item, or one that has been chopped off.
+   */
   if (!state->glyphs)
     {
       pango_layout_get_item_properties (item, &state->properties);
       state->glyphs = shape_run (line, state, item);
-
       state->log_widths_offset = 0;
-
       processing_new_item = TRUE;
     }
+  else
+    processing_new_item = FALSE;
+
+  /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 5.0;
+   * update this if that changes.
+   */
+#define LINE_SEPARATOR 0x2028
 
   if (!layout->single_paragraph &&
       g_utf8_get_char (layout->text + item->offset) == LINE_SEPARATOR &&
       !should_ellipsize_current_line (layout, state))
     {
-      insert_run (line, state, item, TRUE);
+      insert_run (line, state, item, NULL, TRUE);
       state->log_widths_offset += item->num_chars;
 
       return BREAK_LINE_SEPARATOR;
@@ -3853,18 +3911,19 @@ process_item (PangoLayout     *layout,
 
   if (state->remaining_width < 0 && !no_break_at_end)  /* Wrapping off */
     {
-      insert_run (line, state, item, TRUE);
+      insert_run (line, state, item, NULL, TRUE);
 
+      g_debug ("no wrapping, all-fit");
       return BREAK_ALL_FIT;
     }
 
-  width = 0;
   if (processing_new_item)
     {
       width = pango_glyph_string_get_width (state->glyphs);
     }
   else
     {
+      width = 0;
       for (i = 0; i < item->num_chars; i++)
         width += state->log_widths[state->log_widths_offset + i];
     }
@@ -3872,7 +3931,9 @@ process_item (PangoLayout     *layout,
   if ((width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) &&
       !no_break_at_end)
     {
-      insert_run (line, state, item, FALSE);
+      g_debug ("%d <= %d", width, state->remaining_width);
+      insert_run (line, state, item, NULL, FALSE);
+
       width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
 
       if (width <= state->remaining_width || (item->num_chars == 1 && !line->runs))
@@ -3880,9 +3941,13 @@ process_item (PangoLayout     *layout,
           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;
 
+          g_debug ("early accept '%.*s', all-fit, remaining %d",
+                   item->length, layout->text + item->offset,
+                   state->remaining_width);
           return BREAK_ALL_FIT;
         }
 
@@ -3890,197 +3955,201 @@ process_item (PangoLayout     *layout,
       uninsert_run (line);
     }
 
-    {
-      int num_chars;
-      int break_num_chars = item->num_chars;
-      int break_width = width;
-      int orig_width = width;
-      int break_extra_width = 0;
-      gboolean retrying_with_char_breaks = FALSE;
-      gboolean *break_disabled;
+  /*** From here on, we look for a way to break item ***/
 
-      if (processing_new_item)
-        {
-          PangoGlyphItem glyph_item = {item, state->glyphs};
-          if (item->num_chars > state->num_log_widths)
-            {
-              state->log_widths = g_renew (int, state->log_widths, item->num_chars);
-              state->num_log_widths = item->num_chars;
-            }
-          pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
-        }
-
-      break_disabled = g_alloca (sizeof (gboolean) * (item->num_chars + 1));
-      memset (break_disabled, 0, sizeof (gboolean) * (item->num_chars + 1));
-
-    retry_break:
-
-      /* break_extra_width gets normally set from find_break_extra_width inside
-       * the loop, and that takes a space before the break into account. The
-       * one case that is not covered by that is if we end up going all the way
-       * through the loop without ever entering the can_break_at case, and come
-       * out at the other end with the break_extra_width value untouched. So
-       * initialize it here, taking space-before-break into account.
-       */
-      if (layout->log_attrs[state->start_offset + break_num_chars - 1].is_white)
-        {
-          break_extra_width = - state->log_widths[state->log_widths_offset + break_num_chars - 1];
-
-          /* check one more time if the whole item fits after removing the space */
-          if (width + break_extra_width <= state->remaining_width && !no_break_at_end)
-            {
-              insert_run (line, state, item, FALSE);
-              width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
-
-              if (width + break_extra_width <= state->remaining_width)
-                {
-                  state->remaining_width -= width + break_extra_width;
-                  state->remaining_width = MAX (state->remaining_width, 0);
-
-                  pango_glyph_string_free (state->glyphs);
-                  state->glyphs = NULL;
-
-                  return BREAK_ALL_FIT;
-                }
+  orig_width = width;
+  orig_extra_width = extra_width;
+  break_width = width;
+  break_extra_width = extra_width;
+  break_num_chars = item->num_chars;
+  wrap = layout->wrap;
+  break_glyphs = NULL;
 
-              uninsert_run (line);
-            }
-        }
+  /* Add some safety margin here. If we are farther away from the end of the
+   * line than this, we don't look carefully at a break possibility.
+   */
+  metrics = pango_font_get_metrics (item->analysis.font, item->analysis.language);
+  safe_distance = pango_font_metrics_get_approximate_char_width (metrics) * 3;
+  pango_font_metrics_unref (metrics);
 
-      /* See how much of the item we can stuff in the line. */
-      width = 0;
+  if (processing_new_item)
+    {
+      compute_log_widths (layout, state);
+      processing_new_item = FALSE;
+    }
 
-      for (num_chars = 0; num_chars < item->num_chars; num_chars++)
-        {
-          extra_width = find_break_extra_width (layout, state, num_chars);
-
-          /* We don't want to walk the entire item if we can help it, but
-           * we need to keep going at least until we've found a breakpoint
-           * that 'works' (as in, it doesn't overflow the budget we have,
-           * or there is no hope of finding a better one).
-           *
-           * We rely on the fact that MIN(width + extra_width, width) is
-           * monotonically increasing.
-           */
-          if (MIN (width + extra_width, width) > state->remaining_width &&
-              break_num_chars < item->num_chars &&
-              (break_width + break_extra_width <= state->remaining_width ||
-               MIN (width + extra_width, width) > break_width + break_extra_width))
-            {
-              break;
-            }
+retry_break:
 
-          /* If there are no previous runs we have to take care to grab at least one char. */
-          if (!break_disabled[num_chars] &&
-              can_break_at (layout, state->start_offset + num_chars, retrying_with_char_breaks) &&
-              (num_chars > 0 || line->runs))
-            {
-              /* If we had a breakpoint already, we only want to replace it with a better one. */
-              if (width + extra_width <= state->remaining_width ||
-                  width + extra_width < break_width + break_extra_width ||
-                  (width + extra_width == break_width + break_extra_width &&
-                   num_chars > break_num_chars))
-                {
-                  break_num_chars = num_chars;
-                  break_width = width;
-                  break_extra_width = extra_width;
-                }
-            }
+  for (num_chars = 0, width = 0; num_chars < (no_break_at_end ? item->num_chars : (item->num_chars + 1)); 
num_chars++)
+    {
+      extra_width = find_break_extra_width (layout, state, num_chars);
 
-          width += state->log_widths[state->log_widths_offset + num_chars];
-        }
+      /* We don't want to walk the entire item if we can help it, but
+       * we need to keep going at least until we've found a breakpoint
+       * that 'works' (as in, it doesn't overflow the budget we have,
+       * or there is no hope of finding a better one).
+       *
+       * We rely on the fact that MIN(width + extra_width, width) is
+       * monotonically increasing.
+       */
 
-      if (layout->wrap == PANGO_WRAP_WORD_CHAR && force_fit && break_width + break_extra_width > 
state->remaining_width && !retrying_with_char_breaks)
+      if (MIN (width + extra_width, width) > state->remaining_width + safe_distance &&
+          break_num_chars < item->num_chars)
         {
-          retrying_with_char_breaks = TRUE;
-          break_num_chars = item->num_chars;
-          width = orig_width;
-          break_width = width;
-          goto retry_break;
+          g_debug ("at %d, MIN(%d, %d + %d) > %d + MARGIN, breaking at %d",
+                   num_chars, width, extra_width, width, state->remaining_width, break_num_chars);
+          break;
         }
 
-      if (force_fit || break_width + break_extra_width <= state->remaining_width)       /* Successfully 
broke the item */
+      /* If there are no previous runs we have to take care to grab at least one char. */
+      if (can_break_at (layout, state->start_offset + num_chars, wrap) &&
+          (num_chars > 0 || line->runs))
         {
-          int remaining = state->remaining_width;
-
-          if (state->remaining_width >= 0)
-            {
-              state->remaining_width -= break_width;
-              state->remaining_width = MAX (state->remaining_width, 0);
-            }
-
-          if (break_num_chars == item->num_chars)
-            {
-              if (break_needs_hyphen (layout, state, break_num_chars))
-                item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
-              insert_run (line, state, item, TRUE);
-
-              return BREAK_ALL_FIT;
-            }
-          else if (break_num_chars == 0)
+          g_debug ("possible breakpoint: %d", num_chars);
+          if (num_chars == 0 ||
+              width + extra_width < state->remaining_width - safe_distance)
             {
-              return BREAK_EMPTY_FIT;
+              g_debug ("trivial accept");
+              break_num_chars = num_chars;
+              break_width = width;
+              break_extra_width = extra_width;
             }
           else
             {
+              int length;
+              int new_break_width;
               PangoItem *new_item;
+              PangoGlyphString *glyphs;
 
-              length = g_utf8_offset_to_pointer (layout->text + item->offset, break_num_chars) - 
(layout->text + item->offset);
+              length = g_utf8_offset_to_pointer (layout->text + item->offset, num_chars) - (layout->text + 
item->offset);
 
-              new_item = pango_item_split (item, length, break_num_chars);
+              if (num_chars < item->num_chars)
+                {
+                  new_item = pango_item_split (item, length, num_chars);
 
-              if (break_needs_hyphen (layout, state, break_num_chars))
-                new_item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
+                  if (break_needs_hyphen (layout, state, num_chars))
+                    new_item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
+                  else
+                    new_item->analysis.flags &= ~PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
+                }
               else
-                new_item->analysis.flags &= ~PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
+                new_item = item;
 
-              /* Add the width back, to the line, reshape, subtract the new width */
-              state->remaining_width = remaining;
-              insert_run (line, state, new_item, FALSE);
+              glyphs = shape_run (line, state, new_item);
 
-              break_width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
+              new_break_width = pango_glyph_string_get_width (glyphs);
 
-              /* After the shaping, break_width includes a possible hyphen.
-               * We subtract break_extra_width to account for that.
-               */
               if (new_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
-                break_width -= break_extra_width;
+                new_break_width -= extra_width;
 
-              if (break_width + break_extra_width > state->remaining_width &&
-                  !break_disabled[break_num_chars])
+              g_debug ("measured breakpoint %d: %d", num_chars, new_break_width);
+
+              if (new_item != item)
                 {
-                  /* Unsplit the item, disable the breakpoint, try find a better one.
-                   *
-                   * If we can't find a different breakpoint that works better, we'll
-                   * end up here again, with break_disabled being set, and take the break
-                   */
-                  uninsert_run (line);
                   pango_item_free (new_item);
-                  pango_item_unsplit (item, length, break_num_chars);
+                  pango_item_unsplit (item, length, num_chars);
+                }
 
-                  break_disabled[break_num_chars] = TRUE;
+              if (break_num_chars == item->num_chars ||
+                  new_break_width + extra_width <= state->remaining_width ||
+                  new_break_width + extra_width <= break_width + break_extra_width)
+                {
+                  g_debug ("accept breakpoint %d: %d + %d <= %d + %d",
+                           num_chars, new_break_width, extra_width, break_width, break_extra_width);
+                  g_debug ("replace bp %d by %d", break_num_chars, num_chars);
+                  break_num_chars = num_chars;
+                  break_width = new_break_width;
+                  break_extra_width = extra_width;
 
-                  goto retry_break;
+                  if (break_glyphs)
+                    pango_glyph_string_free (break_glyphs);
+                  break_glyphs = glyphs;
                 }
+            }
+        }
 
-              state->remaining_width -= break_width;
+      g_debug ("bp now %d", break_num_chars);
+      if (num_chars < item->num_chars)
+        width += state->log_widths[state->log_widths_offset + num_chars];
+    }
 
-              state->log_widths_offset += break_num_chars;
+   if (wrap == PANGO_WRAP_WORD_CHAR && force_fit && break_width + break_extra_width > state->remaining_width)
+    {
+      /* Try again, with looser conditions */
+      g_debug ("does not fit, try again with wrap-char");
+      wrap = PANGO_WRAP_CHAR;
+      break_num_chars = item->num_chars;
+      break_width = orig_width;
+      break_extra_width = orig_extra_width;
+      if (break_glyphs)
+        pango_glyph_string_free (break_glyphs);
+      break_glyphs = NULL;
+      goto retry_break;
+    }
 
-              /* Shaped items should never be broken */
-              g_assert (!shape_set);
+  if (force_fit || break_width + break_extra_width <= state->remaining_width)       /* Successfully broke 
the item */
+    {
+      if (state->remaining_width >= 0)
+        {
+          state->remaining_width -= break_width + break_extra_width;
+          state->remaining_width = MAX (state->remaining_width, 0);
+        }
 
-              return BREAK_SOME_FIT;
-            }
+      if (break_num_chars == item->num_chars)
+        {
+          if (break_needs_hyphen (layout, state, break_num_chars))
+            item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN;
+
+          insert_run (line, state, item, NULL, TRUE);
+
+          if (break_glyphs)
+            pango_glyph_string_free (break_glyphs);
+
+          g_debug ("all-fit '%.*s', remaining %d",
+                   item->length, layout->text + item->offset,
+                   state->remaining_width);
+          return BREAK_ALL_FIT;
+        }
+      else if (break_num_chars == 0)
+        {
+          if (break_glyphs)
+            pango_glyph_string_free (break_glyphs);
+
+          g_debug ("empty-fit, remaining %d", state->remaining_width);
+          return BREAK_EMPTY_FIT;
         }
       else
         {
-          pango_glyph_string_free (state->glyphs);
-          state->glyphs = NULL;
+          PangoItem *new_item;
 
-          return BREAK_NONE_FIT;
+          length = g_utf8_offset_to_pointer (layout->text + item->offset, break_num_chars) - (layout->text + 
item->offset);
+
+          new_item = pango_item_split (item, length, break_num_chars);
+
+          insert_run (line, state, new_item, break_glyphs, FALSE);
+
+          state->log_widths_offset += break_num_chars;
+
+          /* Shaped items should never be broken */
+          g_assert (!shape_set);
+
+          g_debug ("some-fit '%.*s', remaining %d",
+                   new_item->length, layout->text + new_item->offset,
+                   state->remaining_width);
+          return BREAK_SOME_FIT;
         }
     }
+  else
+    {
+      pango_glyph_string_free (state->glyphs);
+      state->glyphs = NULL;
+
+      if (break_glyphs)
+        pango_glyph_string_free (break_glyphs);
+
+      g_debug ("none-fit, remaining %d", state->remaining_width);
+      return BREAK_NONE_FIT;
+    }
 }
 
 /* The resolved direction for the line is always one
@@ -4293,6 +4362,7 @@ process_line (PangoLayout    *layout,
 
  done:
   pango_layout_line_postprocess (line, state, wrapped);
+  g_debug ("line %d done. remaining %d", state->line_of_par, state->remaining_width);
   add_line (line, state);
   state->line_of_par++;
   state->line_start_index += line->length;
@@ -4558,6 +4628,7 @@ pango_layout_check_lines (PangoLayout *layout)
   state.num_log_widths = 0;
   state.baseline_shifts = NULL;
 
+  g_debug ("START layout");
   do
     {
       int delim_len;
@@ -4684,6 +4755,10 @@ pango_layout_check_lines (PangoLayout *layout)
 
   pango_attr_list_unref (shape_attrs);
   pango_attr_list_unref (attrs);
+
+  int w, h;
+  pango_layout_get_size (layout, &w, &h);
+  g_debug ("DONE %d %d", w, h);
 }
 
 #pragma GCC diagnostic pop
@@ -5838,14 +5913,12 @@ is_tab_run (PangoLayout    *layout,
 }
 
 static void
-zero_line_final_space (PangoLayoutLine *line,
-                       ParaBreakState  *state,
-                       PangoLayoutRun  *run)
+add_missing_hyphen (PangoLayoutLine *line,
+                    ParaBreakState  *state,
+                    PangoLayoutRun  *run)
 {
   PangoLayout *layout = line->layout;
   PangoItem *item = run->item;
-  PangoGlyphString *glyphs;
-  int glyph;
   int line_chars;
 
   line_chars = 0;
@@ -5882,24 +5955,46 @@ zero_line_final_space (PangoLayoutLine *line,
 
       state->remaining_width += pango_glyph_string_get_width (run->glyphs) - width;
     }
+}
+
+static void
+zero_line_final_space (PangoLayoutLine *line,
+                       ParaBreakState  *state,
+                       PangoLayoutRun  *run)
+{
+  PangoLayout *layout = line->layout;
+  PangoItem *item = run->item;
+  PangoGlyphString *glyphs;
+  int glyph;
 
   glyphs = run->glyphs;
   glyph = item->analysis.level % 2 ? 0 : glyphs->num_glyphs - 1;
 
   if (glyphs->glyphs[glyph].glyph == PANGO_GET_UNKNOWN_GLYPH (0x2028))
-    return; /* this LS is visible */
+    {
+      g_debug ("zero final space: visible space");
+      return; /* this LS is visible */
+    }
 
   /* if the final char of line forms a cluster, and it's
    * a whitespace char, zero its glyph's width as it's been wrapped
    */
   if (glyphs->num_glyphs < 1 || state->start_offset == 0 ||
       !layout->log_attrs[state->start_offset - 1].is_white)
-    return;
+    {
+      g_debug ("zero final space: not whitespace");
+      return;
+    }
 
   if (glyphs->num_glyphs >= 2 &&
       glyphs->log_clusters[glyph] == glyphs->log_clusters[glyph + (item->analysis.level % 2 ? 1 : -1)])
-    return;
+    {
 
+      g_debug ("zero final space: its a cluster");
+      return;
+    }
+
+  g_debug ("zero line final space: collapsing the space");
   state->remaining_width += glyphs->glyphs[glyph].geometry.width;
   glyphs->glyphs[glyph].geometry.width = 0;
   glyphs->glyphs[glyph].glyph = PANGO_GLYPH_EMPTY;
@@ -6400,23 +6495,23 @@ pango_layout_line_postprocess (PangoLayoutLine *line,
 {
   gboolean ellipsized = FALSE;
   
-  DEBUG ("postprocessing", line, state);
+  g_debug ("postprocessing line, %s", wrapped ? "wrapped" : "not wrapped");
 
-  /* Truncate the logical-final whitespace in the line if we broke the line at it
-   */
+  add_missing_hyphen (line, state, line->runs->data);
+  DEBUG ("after hyphen addition", line, state);
+
+  /* Truncate the logical-final whitespace in the line if we broke the line at it */
   if (wrapped)
-    /* The runs are in reverse order at this point, since we prepended them to the list.
-     * So, the first run is the last logical run. */
     zero_line_final_space (line, state, line->runs->data);
 
-  /* Reverse the runs
-   */
+  DEBUG ("after removing final space", line, state);
+
+  /* Reverse the runs */
   line->runs = g_slist_reverse (line->runs);
 
   apply_baseline_shift (line, state);
 
-  /* Ellipsize the line if necessary
-   */
+  /* Ellipsize the line if necessary */
   if (G_UNLIKELY (state->line_width >= 0 &&
                   should_ellipsize_current_line (line->layout, state)))
     {
@@ -6428,22 +6523,17 @@ pango_layout_line_postprocess (PangoLayoutLine *line,
       ellipsized = _pango_layout_line_ellipsize (line, state->attrs, shape_flags, state->line_width);
     }
 
-  DEBUG ("after removing final space", line, state);
-
-  /* Now convert logical to visual order
-   */
+  /* Now convert logical to visual order */
   pango_layout_line_reorder (line);
 
   DEBUG ("after reordering", line, state);
 
-  /* Fixup letter spacing between runs
-   */
+  /* Fixup letter spacing between runs */
   adjust_line_letter_spacing (line, state);
 
   DEBUG ("after letter spacing", line, state);
 
-  /* Distribute extra space between words if justifying and line was wrapped
-   */
+  /* Distribute extra space between words if justifying and line was wrapped */
   if (line->layout->justify && (wrapped || ellipsized || line->layout->justify_last_line))
     {
       /* if we ellipsized, we don't have remaining_width set */


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