[pango/break-tailoring: 16/19] Add a line-break attribute




commit 89cc10eb43e11bdec45a8f5cc63208c209d6e8cf
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Aug 21 11:47:33 2021 -0400

    Add a line-break attribute
    
    Add a line-break attribute that can be used to override the
    line break classification at the beginning and end of the
    attribute range.
    
    Tests included.

 docs/pango_markup.md         |  6 ++++
 pango/break.c                | 56 ++++++++++++++++++++++++++++++++++++
 pango/pango-attributes.c     | 42 +++++++++++++++++++++++++++
 pango/pango-attributes.h     | 28 ++++++++++++++++++
 pango/pango-layout.c         |  1 +
 pango/pango-markup.c         | 21 ++++++++++++++
 tests/breaks/twelve.break    |  2 ++
 tests/breaks/twelve.expected |  6 ++++
 tests/test-common.c          |  5 ++++
 tests/testattributes.c       | 67 ++++++++++++++++++++++++++++++++++++++++++--
 10 files changed, 232 insertions(+), 2 deletions(-)
---
diff --git a/docs/pango_markup.md b/docs/pango_markup.md
index 3a1cc311..d35c2a18 100644
--- a/docs/pango_markup.md
+++ b/docs/pango_markup.md
@@ -190,6 +190,12 @@ allow_breaks
 : 'true' or 'false' to indicate whether breaking lines is allowed. Available
   since Pango 1.44.
 
+break_before
+break_after
+: The value can be one of 'none','char', 'line', or 'mandatory', to override
+  the line break classification at the beginning or end of the span. Available
+  since Pango 1.50.
+
 line_height
 : Overrides the line height. The value can be either a factor (< 1024) that is
   used to scale up the logical extents of runs or an absolute value (in 1024th
diff --git a/pango/break.c b/pango/break.c
index 826271db..533fbb51 100644
--- a/pango/break.c
+++ b/pango/break.c
@@ -1633,10 +1633,12 @@ break_attrs (const char   *text,
              int           log_attrs_len)
 {
   PangoAttrList allow_breaks;
+  PangoAttrList line_breaks;
   GSList *l;
   gboolean tailored = FALSE;
 
   _pango_attr_list_init (&allow_breaks);
+  _pango_attr_list_init (&line_breaks);
 
   for (l = attributes; l; l = l->next)
     {
@@ -1644,6 +1646,8 @@ break_attrs (const char   *text,
 
       if (attr->klass->type == PANGO_ATTR_ALLOW_BREAKS)
         pango_attr_list_insert (&allow_breaks, pango_attribute_copy (attr));
+      else if (attr->klass->type == PANGO_ATTR_LINE_BREAK)
+        pango_attr_list_insert (&line_breaks, pango_attribute_copy (attr));
     }
 
   if (_pango_attr_list_has_attributes (&allow_breaks))
@@ -1687,7 +1691,54 @@ break_attrs (const char   *text,
       _pango_attr_iterator_destroy (&iter);
     }
 
+  if (_pango_attr_list_has_attributes (&line_breaks))
+    {
+      PangoAttrIterator iter;
+
+      _pango_attr_list_get_iterator (&line_breaks, &iter);
+      do
+        {
+          const PangoAttribute *attr = pango_attr_iterator_get (&iter, PANGO_ATTR_LINE_BREAK);
+          PangoLineBreak before;
+          PangoLineBreak after;
+
+          if (!attr)
+            continue;
+
+          before = ((PangoAttrInt*)attr)->value & 0xfff;
+          after = ((PangoAttrInt*)attr)->value >> 16;
+
+          if (attr->start_index >= offset && before != 0)
+            {
+              int pos;
+
+              pos = g_utf8_pointer_to_offset (text, text + attr->start_index - offset);
+
+              log_attrs[pos].is_char_break = TRUE;
+              log_attrs[pos].is_line_break = before >= PANGO_LINE_BREAK_LINE;
+              log_attrs[pos].is_mandatory_break = before == PANGO_LINE_BREAK_MANDATORY;
+              tailored = TRUE;
+           }
+
+          if (attr->end_index < offset + length && after != 0)
+            {
+              int pos;
+
+              pos = g_utf8_pointer_to_offset (text, text + attr->end_index - offset);
+
+              log_attrs[pos].is_char_break = TRUE;
+              log_attrs[pos].is_line_break = after >= PANGO_LINE_BREAK_LINE;
+              log_attrs[pos].is_mandatory_break = after == PANGO_LINE_BREAK_MANDATORY;
+              tailored = TRUE;
+            }
+        }
+      while (pango_attr_iterator_next (&iter));
+
+      _pango_attr_iterator_destroy (&iter);
+    }
+
   _pango_attr_list_destroy (&allow_breaks);
+  _pango_attr_list_destroy (&line_breaks);
 
   return tailored;
 }
@@ -1844,6 +1895,11 @@ pango_tailor_break (const char    *text,
  * The line breaks are assumed to have been produced
  * by [func@Pango.default_break] and [func@Pango.tailor_break].
  *
+ * Note that `PANGO_ATTR_ALLOW_BREAKS` attributes are applied
+ * before `PANGO_ATTR_LINE_BREAK` attributes, so it is possible
+ * to remove automatically determined breaks opportunities and
+ * then selectively introduce new ones.
+ *
  * Since: 1.50
  */
 void
diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c
index 28dc4105..501203f3 100644
--- a/pango/pango-attributes.c
+++ b/pango/pango-attributes.c
@@ -1302,6 +1302,47 @@ pango_attr_show_new (PangoShowFlags flags)
   return pango_attr_int_new (&klass, (int)flags);
 }
 
+/**
+ * pango_attr_line_break_new:
+ * @before: override for the beginning of the range
+ * @after: override for the end of the range
+ *
+ * Overrides the line break classification at the beginning
+ * and end of the range.
+ *
+ * The two values determine what line break opportunities
+ * to provide at the beginning and end of the attributes range.
+ *
+ * `PANGO_LINE_BREAK_IGNORE`
+ * : Don't change the line break classification.
+ * `PANGO_LINE_BREAK_NONE`
+ * : No line break opportunity.
+ * `PANGO_LINE_BEAK_CHAR`
+ * : A line break opportunities during character breaking.
+ * `PANGO_LINE_BREAK_LINE`
+ * : A regular line break opportunity.
+ * `PANGO_LINE_BREAK_MANDATORY:
+ * : A mandatory line break.
+ *
+ * Return value: (transfer full): the newly allocated
+ *   `PangoAttribute`, which should be freed with
+ *   [method@Pango.Attribute.destroy]
+ *
+ * Since: 1.50
+ */
+PangoAttribute *
+pango_attr_line_break_new (PangoLineBreak before,
+                           PangoLineBreak after)
+{
+  static const PangoAttrClass klass = {
+    PANGO_ATTR_LINE_BREAK,
+    pango_attr_int_copy,
+    pango_attr_int_destroy,
+    pango_attr_int_equal,
+  };
+
+  return pango_attr_int_new (&klass, before | (after << 16));
+}
 /**
  * pango_attr_overline_new:
  * @overline: the overline style
@@ -1472,6 +1513,7 @@ pango_attribute_as_int (PangoAttribute *attr)
     case PANGO_ATTR_FOREGROUND_ALPHA:
     case PANGO_ATTR_BACKGROUND_ALPHA:
     case PANGO_ATTR_ALLOW_BREAKS:
+    case PANGO_ATTR_LINE_BREAK:
     case PANGO_ATTR_SHOW:
     case PANGO_ATTR_INSERT_HYPHENS:
     case PANGO_ATTR_OVERLINE:
diff --git a/pango/pango-attributes.h b/pango/pango-attributes.h
index 86826b62..c043947d 100644
--- a/pango/pango-attributes.h
+++ b/pango/pango-attributes.h
@@ -77,6 +77,7 @@ typedef struct _PangoAttrFontFeatures PangoAttrFontFeatures;
  * @PANGO_ATTR_OVERLINE_COLOR: overline color ([struct@Pango.AttrColor]). Since 1.46
  * @PANGO_ATTR_LINE_HEIGHT: line height factor ([struct@Pango.AttrFloat]). Since: 1.50
  * @PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: line height ([struct@Pango.AttrInt]). Since: 1.50
+ * @PANGO_ATTR_LINE_BREAK: override line breaks at the ends of the range ([struct@Pango.AttrInt]). Since 1.50
  *
  * The `PangoAttrType` distinguishes between different types of attributes.
  *
@@ -121,6 +122,7 @@ typedef enum
   PANGO_ATTR_LINE_HEIGHT,       /* PangoAttrFloat */
   PANGO_ATTR_ABSOLUTE_LINE_HEIGHT, /* PangoAttrInt */
   PANGO_ATTR_TEXT_TRANSFORM,    /* PangoAttrInt */
+  PANGO_ATTR_LINE_BREAK,        /* PangoAttrInt */
 } PangoAttrType;
 
 /**
@@ -222,6 +224,28 @@ typedef enum {
   PANGO_TEXT_TRANSFORM_CAPITALIZE,
 } PangoTextTransform;
 
+/**
+ * PangoLineBreak:
+ * @PANGO_LINE_BREAK_IGNORE: An ignored value
+ * @PANGO_LINE_BREAK_NONE: No line break opportunity
+ * @PANGO_LINE_BREAK_CHAR: Line break opportunity when doing character wrapping
+ * @PANGO_LINE_BREAK_LINE: Line break opportunity
+ * @PANGO_LINE_BREAK_MANDATORY: Mandatory break
+ *
+ * These values are used in attributes that modify line break classification.
+ *
+ * The `PANGO_ATTR_LINE_BREAK` attribute combines two `PangoLineBreak`
+ * values into its value, one for the start of the range, and another
+ * for the end of the range, shifted by 16.
+ */
+typedef enum {
+  PANGO_LINE_BREAK_IGNORE,
+  PANGO_LINE_BREAK_NONE,
+  PANGO_LINE_BREAK_CHAR,
+  PANGO_LINE_BREAK_LINE,
+  PANGO_LINE_BREAK_MANDATORY,
+} PangoLineBreak;
+
 /**
  * PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING:
  *
@@ -538,6 +562,10 @@ PANGO_AVAILABLE_IN_1_38
 PangoAttribute *        pango_attr_background_alpha_new         (guint16                      alpha);
 PANGO_AVAILABLE_IN_1_44
 PangoAttribute *        pango_attr_allow_breaks_new             (gboolean                     allow_breaks);
+PANGO_AVAILABLE_IN_1_50
+PangoAttribute *        pango_attr_line_break_new               (PangoLineBreak               before,
+                                                                 PangoLineBreak               after);
+
 PANGO_AVAILABLE_IN_1_44
 PangoAttribute *        pango_attr_insert_hyphens_new           (gboolean                     
insert_hyphens);
 PANGO_AVAILABLE_IN_1_46
diff --git a/pango/pango-layout.c b/pango/pango-layout.c
index b6e0c217..c1d9343a 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -4324,6 +4324,7 @@ affects_break_or_shape (PangoAttribute *attr,
     {
     /* Affects breaks */
     case PANGO_ATTR_ALLOW_BREAKS:
+    case PANGO_ATTR_LINE_BREAK:
     /* Affects shaping */
     case PANGO_ATTR_INSERT_HYPHENS:
     case PANGO_ATTR_FONT_FEATURES:
diff --git a/pango/pango-markup.c b/pango/pango-markup.c
index 22064103..f9c143c6 100644
--- a/pango/pango-markup.c
+++ b/pango/pango-markup.c
@@ -1230,6 +1230,8 @@ span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
   const char *show = NULL;
   const char *line_height = NULL;
   const char *text_transform = NULL;
+  const char *break_before = NULL;
+  const char *break_after = NULL;
 
   g_markup_parse_context_get_position (context,
                                       &line_number, &char_number);
@@ -1267,6 +1269,8 @@ span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
        CHECK_ATTRIBUTE2(background, "bgcolor");
         CHECK_ATTRIBUTE (background_alpha);
         CHECK_ATTRIBUTE2(background_alpha, "bgalpha");
+        CHECK_ATTRIBUTE (break_before);
+        CHECK_ATTRIBUTE (break_after);
         break;
       case 'c':
        CHECK_ATTRIBUTE2(foreground, "color");
@@ -1717,6 +1721,23 @@ span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
       add_attribute (tag, pango_attr_allow_breaks_new (b));
     }
 
+  if (G_UNLIKELY (break_before || break_after))
+    {
+      PangoLineBreak before, after;
+
+      if (!break_before)
+        before = 0;
+      else if (!span_parse_enum ("break_before", break_before, PANGO_TYPE_LINE_BREAK, (int*)(void*)&before, 
line_number, error))
+        goto error;
+
+      if (!break_after)
+        after = 0;
+      else if (!span_parse_enum ("break_after", break_after, PANGO_TYPE_LINE_BREAK, (int*)(void*)&after, 
line_number, error))
+        goto error;
+
+      add_attribute (tag, pango_attr_line_break_new (before, after));
+    }
+
   if (G_UNLIKELY (insert_hyphens))
     {
       gboolean b = FALSE;
diff --git a/tests/breaks/twelve.break b/tests/breaks/twelve.break
new file mode 100644
index 00000000..cf3c8aa1
--- /dev/null
+++ b/tests/breaks/twelve.break
@@ -0,0 +1,2 @@
+# test line break attributes
+the file <span allow_breaks='false'><span break_before='mandatory' break_after='line'>/path/</span><span 
break_after='line'>to/</span><span break_after='line'>my/</span>home</span> is cursed.
diff --git a/tests/breaks/twelve.expected b/tests/breaks/twelve.expected
new file mode 100644
index 00000000..3d9bd4ae
--- /dev/null
+++ b/tests/breaks/twelve.expected
@@ -0,0 +1,6 @@
+Text:         ⁦t⁩ ⁦h⁩ ⁦e⁩  [ ]  ⁦f⁩ ⁦i⁩ ⁦l⁩ ⁦e⁩  [ ]  ⁦/⁩  ⁦p⁩ ⁦a⁩ ⁦t⁩ ⁦h⁩  ⁦/⁩  ⁦t⁩ ⁦o⁩  ⁦/⁩  ⁦m⁩ ⁦y⁩  ⁦/⁩  
⁦h⁩ ⁦o⁩ ⁦m⁩ ⁦e⁩  [ ]  ⁦i⁩ ⁦s⁩  [ ]  ⁦c⁩ ⁦u⁩ ⁦r⁩ ⁦s⁩ ⁦e⁩ ⁦d⁩  ⁦.⁩ [0x0a] 
+Breaks:     c  c c c    lc c c c c    Lc             lc      lc      lc       c    lc c c    lc c c c c c c  
c      c
+Whitespace:        x             x                                            x         x                    
w      w
+Sentences:  bs                                                                                               
e      b
+Words:      bs     be   bs       be   b  bs       be bs   be bs   be bs       be   bs   be   bs           be 
b      b
+Graphemes:  b  b b b    b  b b b b    b  b  b b b b  b  b b  b  b b  b  b b b b    b  b b    b  b b b b b b  
b      b
diff --git a/tests/test-common.c b/tests/test-common.c
index 011b2eef..12ea1cb0 100644
--- a/tests/test-common.c
+++ b/tests/test-common.c
@@ -146,6 +146,11 @@ print_attribute (PangoAttribute *attr, GString *string)
     case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
       g_string_append_printf (string, "%d", ((PangoAttrInt *)attr)->value);
       break;
+    case PANGO_ATTR_LINE_BREAK:
+      g_string_append_printf (string, "%d, %d",
+                              ((PangoAttrInt *)attr)->value & 0xffff,
+                              ((PangoAttrInt *)attr)->value >> 16);
+      break;
     case PANGO_ATTR_FONT_DESC:
       {
         char *text = pango_font_description_to_string (((PangoAttrFontDesc *)attr)->desc);
diff --git a/tests/testattributes.c b/tests/testattributes.c
index f950a204..531fa1cc 100644
--- a/tests/testattributes.c
+++ b/tests/testattributes.c
@@ -75,6 +75,7 @@ test_attributes_basic (void)
   test_copy (pango_attr_text_transform_new (PANGO_TEXT_TRANSFORM_UPPERCASE));
   test_copy (pango_attr_line_height_new (1.5));
   test_copy (pango_attr_line_height_new_absolute (3000));
+  test_copy (pango_attr_line_break_new (PANGO_LINE_BREAK_CHAR, PANGO_LINE_BREAK_MANDATORY));
 }
 
 static void
@@ -207,6 +208,7 @@ test_binding_helpers (void)
   test_binding (pango_attr_text_transform_new (PANGO_TEXT_TRANSFORM_UPPERCASE));
   test_binding (pango_attr_line_height_new (1.5));
   test_binding (pango_attr_line_height_new_absolute (3000));
+  test_binding (pango_attr_line_break_new (PANGO_LINE_BREAK_CHAR, PANGO_LINE_BREAK_MANDATORY));
 }
 
 static void
@@ -1213,8 +1215,8 @@ test_merge2 (void)
   pango_attr_list_unref (list);
 }
 
-/* This only prints rise, size and scale, which are the
- * only relevant attributes in the test that uses this
+/* This only prints rise, size, scale, allow_breaks and line_break,
+ * which are the only relevant attributes in the tests that use this
  * function.
  */
 static void
@@ -1240,6 +1242,19 @@ print_tags_for_attributes (PangoAttrIterator *iter,
     g_string_append_printf (s, "[%d, %d]scale=%f\n",
                             attr->start_index, attr->end_index,
                             ((PangoAttrFloat*)attr)->value);
+
+  attr = pango_attr_iterator_get (iter, PANGO_ATTR_ALLOW_BREAKS);
+  if (attr)
+    g_string_append_printf (s, "[%d, %d]allow_breaks=%d\n",
+                            attr->start_index, attr->end_index,
+                            ((PangoAttrInt*)attr)->value);
+
+  attr = pango_attr_iterator_get (iter, PANGO_ATTR_LINE_BREAK);
+  if (attr)
+    g_string_append_printf (s, "[%d, %d]before=%d,after=%d\n",
+                            attr->start_index, attr->end_index,
+                            ((PangoAttrInt*)attr)->value & 0xffff,
+                            ((PangoAttrInt*)attr)->value >> 16);
 }
 
 static void
@@ -1301,6 +1316,53 @@ test_iter_epsilon_zero (void)
   g_string_free (s, TRUE);
 }
 
+static void
+test_iter_line_breaks (void)
+{
+  const char *markup = "<span allow_breaks='false'>a<span break_before='line'>b</span><span 
break_after='mandatory'>c</span></span>";
+  PangoAttrList *attributes;
+  PangoAttrIterator *attr;
+  char *text;
+  GError *error = NULL;
+  GString *s;
+
+  s = g_string_new ("");
+
+  pango_parse_markup (markup, -1, 0, &attributes, &text, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpstr (text, ==, "abc");
+
+  attr = pango_attr_list_get_iterator (attributes);
+  do
+    {
+      int start, end;
+
+      pango_attr_iterator_range (attr, &start, &end);
+
+      g_string_append_printf (s, "range: [%d, %d]\n", start, end);
+
+      print_tags_for_attributes (attr, s);
+    }
+  while (pango_attr_iterator_next (attr));
+
+  g_free (text);
+  pango_attr_list_unref (attributes);
+  pango_attr_iterator_destroy (attr);
+
+  g_assert_cmpstr (s->str, ==,
+                   "range: [0, 1]\n"
+                   "[0, 3]allow_breaks=0\n"
+                   "range: [1, 2]\n"
+                   "[0, 3]allow_breaks=0\n"
+                   "[1, 2]before=3,after=0\n"
+                   "range: [2, 3]\n"
+                   "[0, 3]allow_breaks=0\n"
+                   "[2, 3]before=0,after=4\n"
+                   "range: [3, 2147483647]\n");
+
+  g_string_free (s, TRUE);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -1340,6 +1402,7 @@ main (int argc, char *argv[])
   g_test_add_func ("/attributes/iter/get_font", test_iter_get_font);
   g_test_add_func ("/attributes/iter/get_attrs", test_iter_get_attrs);
   g_test_add_func ("/attributes/iter/epsilon_zero", test_iter_epsilon_zero);
+  g_test_add_func ("/attributes/iter/line_breaks", test_iter_line_breaks);
 
   return g_test_run ();
 }


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