[pango/break-tailoring: 37/37] Add segmentation attributes
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pango/break-tailoring: 37/37] Add segmentation attributes
- Date: Sun, 22 Aug 2021 05:46:26 +0000 (UTC)
commit ebe99c5ce8434e5fff01f4284ee70a21393b20e8
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Aug 21 21:01:29 2021 -0400
Add segmentation attributes
Add attributes that let us override word and
sentence boundaries.
docs/pango_markup.md | 6 ++
pango/break.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++
pango/pango-attributes.c | 54 +++++++++++++++
pango/pango-attributes.h | 9 +++
pango/pango-layout.c | 2 +
pango/pango-markup.c | 21 ++++++
tests/test-common.c | 2 +
tests/testattributes.c | 6 +-
8 files changed, 274 insertions(+), 1 deletion(-)
---
diff --git a/docs/pango_markup.md b/docs/pango_markup.md
index b84bffef..e91bb5f7 100644
--- a/docs/pango_markup.md
+++ b/docs/pango_markup.md
@@ -209,6 +209,12 @@ text_transform
'none', 'lowercase', 'uppercase' or 'capitalize'. Support for text transformation
was added in Pango 1.50.
+segment
+: Overrides word or sentence boundaries. The value can be 'word' or 'sentence',
+ to indicate that the span should be treated as a single word or sentence.
+ Overlapping segments will be split to allow this.
+ Available since Pango 1.50.
+
## Convenience Tags
`<b>`
diff --git a/pango/break.c b/pango/break.c
index 4fc74b00..ca0cd092 100644
--- a/pango/break.c
+++ b/pango/break.c
@@ -1626,11 +1626,15 @@ break_attrs (const char *text,
{
PangoAttrList allow_breaks;
PangoAttrList line_breaks;
+ PangoAttrList words;
+ PangoAttrList sentences;
GSList *l;
gboolean tailored = FALSE;
_pango_attr_list_init (&allow_breaks);
_pango_attr_list_init (&line_breaks);
+ _pango_attr_list_init (&words);
+ _pango_attr_list_init (&sentences);
for (l = attributes; l; l = l->next)
{
@@ -1651,6 +1655,10 @@ break_attrs (const char *text,
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));
+ else if (attr->klass->type == PANGO_ATTR_WORD)
+ pango_attr_list_insert (&words, pango_attribute_copy (attr));
+ else if (attr->klass->type == PANGO_ATTR_SENTENCE)
+ pango_attr_list_insert (&sentences, pango_attribute_copy (attr));
}
if (_pango_attr_list_has_attributes (&allow_breaks))
@@ -1748,8 +1756,175 @@ break_attrs (const char *text,
_pango_attr_iterator_destroy (&iter);
}
+ if (_pango_attr_list_has_attributes (&words))
+ {
+ PangoAttrIterator iter;
+
+ _pango_attr_list_get_iterator (&words, &iter);
+ do
+ {
+ const PangoAttribute *attr = pango_attr_iterator_get (&iter, PANGO_ATTR_WORD);
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ if (!attr)
+ continue;
+
+ start = attr->start_index;
+ end = attr->end_index;
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ if ((start >= offset && !log_attrs[start_pos].is_cursor_position) ||
+ (end < offset + length && !log_attrs[end_pos].is_cursor_position))
+ {
+ g_warning ("Can't place word boundary on non-grapheme break");
+ continue;
+ }
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ {
+ log_attrs[pos].is_word_start = FALSE;
+ log_attrs[pos].is_word_end = FALSE;
+ log_attrs[pos].is_word_boundary = FALSE;
+ log_attrs[pos].is_sentence_start = FALSE;
+ log_attrs[pos].is_sentence_end = FALSE;
+ log_attrs[pos].is_sentence_boundary = FALSE;
+
+ tailored = TRUE;
+ }
+ if (start >= offset)
+ {
+ gboolean in_word = FALSE;
+ for (pos = start - 1; pos >= offset; pos--)
+ {
+ if (log_attrs[pos].is_word_end)
+ break;
+ if (log_attrs[pos].is_word_start)
+ {
+ in_word = TRUE;
+ break;
+ }
+ }
+ log_attrs[start_pos].is_word_start = TRUE;
+ log_attrs[start_pos].is_word_end = in_word;
+ log_attrs[start_pos].is_word_boundary = TRUE;
+ }
+ if (end < offset + length)
+ {
+ gboolean in_word = FALSE;
+ for (pos = end + 1; pos < offset + length; pos++)
+ {
+ if (log_attrs[pos].is_word_start)
+ break;
+ if (log_attrs[pos].is_word_end)
+ {
+ in_word = TRUE;
+ break;
+ }
+ }
+ log_attrs[end_pos].is_word_start = in_word;
+ log_attrs[end_pos].is_word_end = TRUE;
+ log_attrs[end_pos].is_word_boundary = TRUE;
+ }
+ }
+ while (pango_attr_iterator_next (&iter));
+
+ _pango_attr_iterator_destroy (&iter);
+ }
+
+ if (_pango_attr_list_has_attributes (&sentences))
+ {
+ PangoAttrIterator iter;
+
+ _pango_attr_list_get_iterator (&sentences, &iter);
+ do
+ {
+ const PangoAttribute *attr = pango_attr_iterator_get (&iter, PANGO_ATTR_SENTENCE);
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ if (!attr)
+ continue;
+
+ start = attr->start_index;
+ end = attr->end_index;
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ if ((start >= offset && !log_attrs[start_pos].is_word_boundary) ||
+ (end < offset + length && !log_attrs[end_pos].is_word_boundary))
+ {
+ g_warning ("Can't place sentence boundary on non-word boundary");
+ continue;
+ }
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ {
+ log_attrs[pos].is_sentence_start = FALSE;
+ log_attrs[pos].is_sentence_end = FALSE;
+ log_attrs[pos].is_sentence_boundary = FALSE;
+
+ tailored = TRUE;
+ }
+ if (start >= offset)
+ {
+ gboolean in_sentence = FALSE;
+ for (pos = start - 1; pos >= offset; pos--)
+ {
+ if (log_attrs[pos].is_sentence_end)
+ break;
+ if (log_attrs[pos].is_sentence_start)
+ {
+ in_sentence = TRUE;
+ break;
+ }
+ }
+ log_attrs[start_pos].is_sentence_start = TRUE;
+ log_attrs[start_pos].is_sentence_end = in_sentence;
+ log_attrs[start_pos].is_sentence_boundary = TRUE;
+ }
+ if (end < offset + length)
+ {
+ gboolean in_sentence = FALSE;
+ for (pos = end + 1; pos < offset + length; pos++)
+ {
+ if (log_attrs[pos].is_sentence_start)
+ break;
+ if (log_attrs[pos].is_sentence_end)
+ {
+ in_sentence = TRUE;
+ break;
+ }
+ }
+ log_attrs[end_pos].is_sentence_start = in_sentence;
+ log_attrs[end_pos].is_sentence_end = TRUE;
+ log_attrs[end_pos].is_sentence_boundary = TRUE;
+ }
+ }
+ while (pango_attr_iterator_next (&iter));
+
+ _pango_attr_iterator_destroy (&iter);
+ }
+
_pango_attr_list_destroy (&allow_breaks);
_pango_attr_list_destroy (&line_breaks);
+ _pango_attr_list_destroy (&words);
+ _pango_attr_list_destroy (&sentences);
return tailored;
}
diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c
index 4db2fd81..48c1152b 100644
--- a/pango/pango-attributes.c
+++ b/pango/pango-attributes.c
@@ -1391,6 +1391,60 @@ pango_attr_line_break_new (PangoLineBreak before,
return pango_attr_int_new (&klass, before | (after << 16));
}
+/**
+ * pango_attr_word_new:
+ *
+ * Marks the range of the attribute as a single word.
+ *
+ * Note that this may require adjustments to word and
+ * sentence classification around the range.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `PangoAttribute`, which should be freed with
+ * [method@Pango.Attribute.destroy]
+ *
+ * Since: 1.50
+ */
+PangoAttribute *
+pango_attr_word_new (void)
+{
+ static const PangoAttrClass klass = {
+ PANGO_ATTR_WORD,
+ pango_attr_int_copy,
+ pango_attr_int_destroy,
+ pango_attr_int_equal,
+ };
+
+ return pango_attr_int_new (&klass, 0);
+}
+
+/**
+ * pango_attr_sentence_new:
+ *
+ * Marks the range of the attribute as a single sentence.
+ *
+ * Note that this may require adjustments to word and
+ * sentence classification around the range.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `PangoAttribute`, which should be freed with
+ * [method@Pango.Attribute.destroy]
+ *
+ * Since: 1.50
+ */
+PangoAttribute *
+pango_attr_sentence_new (void)
+{
+ static const PangoAttrClass klass = {
+ PANGO_ATTR_SENTENCE,
+ pango_attr_int_copy,
+ pango_attr_int_destroy,
+ pango_attr_int_equal,
+ };
+
+ return pango_attr_int_new (&klass, 0);
+}
+
/**
* pango_attr_overline_new:
* @overline: the overline style
diff --git a/pango/pango-attributes.h b/pango/pango-attributes.h
index 8d7be7d2..b47287bb 100644
--- a/pango/pango-attributes.h
+++ b/pango/pango-attributes.h
@@ -79,6 +79,8 @@ typedef struct _PangoAttrFontFeatures PangoAttrFontFeatures;
* @PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: line height ([struct@Pango.AttrInt]). Since: 1.50
* @PANGO_ATTR_ALLOW_LINE_BREAKS: what algorithmically determined line breaks to allow
([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
+ * @PANGO_ATTR_WORD: override segmentation to classify the range of the attribute as a single word
([struct@Pango.AttrInt]). Since 1.50
+ * @PANGO_ATTR_SENTENCE: override segmentation to classify the range of the attribute as a single sentence
([struct@Pango.AttrInt]). Since 1.50
*
* The `PangoAttrType` distinguishes between different types of attributes.
*
@@ -125,6 +127,8 @@ typedef enum
PANGO_ATTR_TEXT_TRANSFORM, /* PangoAttrInt */
PANGO_ATTR_ALLOW_LINE_BREAKS, /* PangoAttrInt */
PANGO_ATTR_LINE_BREAK, /* PangoAttrInt */
+ PANGO_ATTR_WORD, /* PangoAttrInt */
+ PANGO_ATTR_SENTENCE, /* PangoAttrInt */
} PangoAttrType;
/**
@@ -569,6 +573,11 @@ PANGO_AVAILABLE_IN_1_50
PangoAttribute * pango_attr_line_break_new (PangoLineBreak before,
PangoLineBreak after);
+PANGO_AVAILABLE_IN_1_50
+PangoAttribute * pango_attr_word_new (void);
+PANGO_AVAILABLE_IN_1_50
+PangoAttribute * pango_attr_sentence_new (void);
+
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 5b1c84da..c4fa24c9 100644
--- a/pango/pango-layout.c
+++ b/pango/pango-layout.c
@@ -4367,6 +4367,8 @@ affects_break_or_shape (PangoAttribute *attr,
case PANGO_ATTR_ALLOW_BREAKS:
case PANGO_ATTR_ALLOW_LINE_BREAKS:
case PANGO_ATTR_LINE_BREAK:
+ case PANGO_ATTR_WORD:
+ case PANGO_ATTR_SENTENCE:
/* 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 80eae858..e15e6998 100644
--- a/pango/pango-markup.c
+++ b/pango/pango-markup.c
@@ -1229,6 +1229,7 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED,
const char *text_transform = NULL;
const char *break_before = NULL;
const char *break_after = NULL;
+ const char *segment = NULL;
g_markup_parse_context_get_position (context,
&line_number, &char_number);
@@ -1298,6 +1299,7 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED,
CHECK_ATTRIBUTE (strikethrough);
CHECK_ATTRIBUTE (strikethrough_color);
CHECK_ATTRIBUTE (style);
+ CHECK_ATTRIBUTE (segment);
break;
case 't':
CHECK_ATTRIBUTE (text_transform);
@@ -1757,6 +1759,25 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED,
add_attribute (tag, pango_attr_insert_hyphens_new (b));
}
+ if (G_UNLIKELY (segment))
+ {
+ if (strcmp (segment, "word") == 0)
+ add_attribute (tag, pango_attr_word_new ());
+ else if (strcmp (segment, "sentence") == 0)
+ add_attribute (tag, pango_attr_sentence_new ());
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ _("Value of 'segment' attribute on <span> tag on line %d "
+ "could not be parsed; should be one of 'word' or "
+ "'sentence', not '%s'"),
+ line_number, segment);
+ goto error;
+ }
+ }
+
return TRUE;
error:
diff --git a/tests/test-common.c b/tests/test-common.c
index c93197eb..1a9c1154 100644
--- a/tests/test-common.c
+++ b/tests/test-common.c
@@ -145,6 +145,8 @@ print_attribute (PangoAttribute *attr, GString *string)
case PANGO_ATTR_SHOW:
case PANGO_ATTR_TEXT_TRANSFORM:
case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT:
+ case PANGO_ATTR_WORD:
+ case PANGO_ATTR_SENTENCE:
g_string_append_printf (string, "%d", ((PangoAttrInt *)attr)->value);
break;
case PANGO_ATTR_LINE_BREAK:
diff --git a/tests/testattributes.c b/tests/testattributes.c
index f5db6e6c..12d72df7 100644
--- a/tests/testattributes.c
+++ b/tests/testattributes.c
@@ -77,6 +77,8 @@ test_attributes_basic (void)
test_copy (pango_attr_line_height_new_absolute (3000));
test_copy (pango_attr_line_break_new (PANGO_LINE_BREAK_CHAR, PANGO_LINE_BREAK_MANDATORY));
test_copy (pango_attr_allow_line_breaks_new (PANGO_LINE_BREAK_LINE));
+ test_copy (pango_attr_word_new ());
+ test_copy (pango_attr_sentence_new ());
}
static void
@@ -136,7 +138,7 @@ test_binding (PangoAttribute *attr)
INVALID, LANGUAGE, STRING, INT, INT, INT, INT, SIZE, FONT_DESC, COLOR,
COLOR, INT, INT, INT, SHAPE, FLOAT, INT, INT, COLOR, COLOR, SIZE,
INT, INT, FONT_FEATURES, INT, INT, INT, INT, INT, INT, COLOR, FLOAT,
- INT, INT, INT, INT
+ INT, INT, INT, INT, INT, INT
};
switch (attr_base[attr->klass->type])
@@ -219,6 +221,8 @@ test_binding_helpers (void)
test_binding (pango_attr_line_height_new_absolute (3000));
test_binding (pango_attr_line_break_new (PANGO_LINE_BREAK_CHAR, PANGO_LINE_BREAK_MANDATORY));
test_binding (pango_attr_allow_line_breaks_new (PANGO_LINE_BREAK_LINE));
+ test_binding (pango_attr_word_new ());
+ test_binding (pango_attr_sentence_new ());
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]