[pango/break-tailoring: 3/4] Add a line-break attribute




commit 5037251696ec7463a4b85a1721a83f1ae531947c
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     | 13 +++++++++
 pango/pango-attributes.h     | 29 +++++++++++++++++--
 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       | 66 ++++++++++++++++++++++++++++++++++++++++++--
 10 files changed, 201 insertions(+), 4 deletions(-)
---
diff --git a/docs/pango_markup.md b/docs/pango_markup.md
index 03718907..8143570d 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 9e0b4537..96fef800 100644
--- a/pango/break.c
+++ b/pango/break.c
@@ -1673,6 +1673,11 @@ pango_break (const gchar   *text,
  * If @offset is not -1, it is used to apply attributes
  * from @analysis that are relevant to line breaking.
  *
+ * 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.44
  */
 void
@@ -1833,10 +1838,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)
     {
@@ -1844,6 +1851,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))
@@ -1887,7 +1896,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;
 }
diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c
index 5690aeaa..8cb19642 100644
--- a/pango/pango-attributes.c
+++ b/pango/pango-attributes.c
@@ -1302,6 +1302,19 @@ pango_attr_show_new (PangoShowFlags flags)
   return pango_attr_int_new (&klass, (int)flags);
 }
 
+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
diff --git a/pango/pango-attributes.h b/pango/pango-attributes.h
index e3012008..62507be5 100644
--- a/pango/pango-attributes.h
+++ b/pango/pango-attributes.h
@@ -120,6 +120,7 @@ typedef enum
   PANGO_ATTR_OVERLINE_COLOR,    /* PangoAttrColor */
   PANGO_ATTR_LINE_HEIGHT,       /* PangoAttrFloat */
   PANGO_ATTR_ABSOLUTE_LINE_HEIGHT, /* PangoAttrInt */
+  PANGO_ATTR_LINE_BREAK,        /* PangoAttrInt */
 } PangoAttrType;
 
 /**
@@ -202,6 +203,27 @@ typedef enum {
   PANGO_SHOW_IGNORABLES  = 1 << 2
 } PangoShowFlags;
 
+/**
+ * PangoLineBreak:
+ * @PANGO_LINE_BREAK_NONE: Don't allow breaking
+ * @PANGO_LINE_BREAK_CHAR: Allow a break when doing character wrapping
+ * @PANGO_LINE_BREAK_LINE: Allow a break
+ * @PANGO_LINE_BREAK_MANDATORY: Mandatory break
+ *
+ * These value specify how to modify line breaks at
+ * the start and end of a range.
+ *
+ * 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_NONE = 1,
+  PANGO_LINE_BREAK_CHAR,
+  PANGO_LINE_BREAK_LINE,
+  PANGO_LINE_BREAK_MANDATORY,
+} PangoLineBreak;
+
 /**
  * PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING:
  *
@@ -214,8 +236,7 @@ typedef enum {
 
 /**
  * PANGO_ATTR_INDEX_TO_TEXT_END: (value 4294967295)
- *
- * Value for @end_index in `PangoAttribute` that indicates
+ * * Value for @end_index in `PangoAttribute` that indicates
  * the end of the text.
  *
  * Since: 1.24
@@ -518,6 +539,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 9e86bcd5..a9b702f0 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -4350,6 +4350,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 a897a52d..75215379 100644
--- a/pango/pango-markup.c
+++ b/pango/pango-markup.c
@@ -1226,6 +1226,8 @@ span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
   const char *insert_hyphens = NULL;
   const char *show = NULL;
   const char *line_height = NULL;
+  const char *break_before = NULL;
+  const char *break_after = NULL;
 
   g_markup_parse_context_get_position (context,
                                       &line_number, &char_number);
@@ -1263,6 +1265,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");
@@ -1700,6 +1704,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..697b9c06
--- /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
+Words:      bs     be   bs       be   b  bs       be bs   be bs   be bs       be   bs   be   bs           be 
b      b
+Sentences:  bs                                                                                               
e      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 78ed722f..52f4cda2 100644
--- a/tests/test-common.c
+++ b/tests/test-common.c
@@ -144,6 +144,11 @@ print_attribute (PangoAttribute *attr, GString *string)
     case PANGO_ATTR_SHOW:
       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 ba4ba7dd..5149766f 100644
--- a/tests/testattributes.c
+++ b/tests/testattributes.c
@@ -69,6 +69,7 @@ test_attributes_basic (void)
   test_copy (pango_attr_allow_breaks_new (FALSE));
   test_copy (pango_attr_show_new (PANGO_SHOW_SPACES));
   test_copy (pango_attr_insert_hyphens_new (FALSE));
+  test_copy (pango_attr_line_break_new (PANGO_LINE_BREAK_CHAR, PANGO_LINE_BREAK_MANDATORY));
 }
 
 static void
@@ -1114,8 +1115,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
@@ -1141,6 +1142,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
@@ -1202,6 +1216,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[])
 {
@@ -1240,6 +1301,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]