diff options
Diffstat (limited to 'pango')
-rw-r--r-- | pango/pango-glyph-item.c | 255 |
1 files changed, 226 insertions, 29 deletions
diff --git a/pango/pango-glyph-item.c b/pango/pango-glyph-item.c index a941f650..a18dd0d9 100644 --- a/pango/pango-glyph-item.c +++ b/pango/pango-glyph-item.c @@ -24,6 +24,8 @@ #include "pango-glyph-item.h" +#define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0) + /** * pango_glyph_item_split: * @orig: a #PangoItem @@ -53,14 +55,13 @@ pango_glyph_item_split (PangoGlyphItem *orig, int num_glyphs; int num_remaining; int split_offset; - gboolean ltr = (orig->item->analysis.level % 2) == 0; g_return_val_if_fail (orig != NULL, NULL); g_return_val_if_fail (orig->item->length > 0, NULL); g_return_val_if_fail (split_index > 0, NULL); g_return_val_if_fail (split_index < orig->item->length, NULL); - if (ltr) + if (LTR (orig)) { for (i = 0; i < orig->glyphs->num_glyphs; i++) { @@ -99,7 +100,7 @@ pango_glyph_item_split (PangoGlyphItem *orig, new->glyphs = pango_glyph_string_new (); pango_glyph_string_set_size (new->glyphs, num_glyphs); - if (ltr) + if (LTR (orig)) { memcpy (new->glyphs->glyphs, orig->glyphs->glyphs, num_glyphs * sizeof (PangoGlyphInfo)); memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters, num_glyphs * sizeof (int)); @@ -123,6 +124,26 @@ pango_glyph_item_split (PangoGlyphItem *orig, return new; } +/* Structure holding state when we're iterating over a GlyphItem for + * pango_glyph_item_apply_attrs(). cluster_start/cluster_end (and + * range_start/range_end in apply_attrs()) are offsets into the + * text, so note the difference of glyph_item->item->offset between + * them and clusters in the log_clusters[] array. + */ +typedef struct +{ + PangoGlyphItem *glyph_item; + const gchar *text; + + int glyph_index; + int cluster_start; + int cluster_end; + + GSList *segment_attrs; +} ApplyAttrsState; + +/* Tack @attrs onto the attributes of glyph_item + */ static void append_attrs (PangoGlyphItem *glyph_item, GSList *attrs) @@ -131,6 +152,101 @@ append_attrs (PangoGlyphItem *glyph_item, g_slist_concat (glyph_item->item->analysis.extra_attrs, attrs); } +/* Make a deep copy of a GSlist of PangoAttribute + */ +static GSList * +attr_slist_copy (GSList *attrs) +{ + GSList *tmp_list; + GSList *new_attrs; + + new_attrs = g_slist_copy (attrs); + + for (tmp_list = new_attrs; tmp_list; tmp_list = tmp_list->next) + tmp_list->data = pango_attribute_copy (tmp_list->data); + + return new_attrs; +} + +/* Advance to the next logical cluster + */ +static gboolean +next_cluster (ApplyAttrsState *state) +{ + int glyph_index = state->glyph_index; + PangoGlyphString *glyphs = state->glyph_item->glyphs; + PangoItem *item = state->glyph_item->item; + + state->cluster_start = state->cluster_end; + + if (LTR (state->glyph_item)) + { + if (glyph_index == glyphs->num_glyphs) + return FALSE; + + while (TRUE) + { + glyph_index++; + + if (glyph_index == glyphs->num_glyphs) + { + state->cluster_end = item->offset + item->length; + break; + } + + if (item->offset + glyphs->log_clusters[glyph_index] >= state->cluster_start) + { + state->cluster_end = item->offset + glyphs->log_clusters[glyph_index]; + break; + } + } + } + else /* RTL */ + { + if (glyph_index < 0) + return FALSE; + + while (TRUE) + { + glyph_index--; + + if (glyph_index < 0) + { + state->cluster_end = item->offset + item->length; + break; + } + + if (item->offset + glyphs->log_clusters[glyph_index] >= state->cluster_start) + { + state->cluster_end = item->offset + glyphs->log_clusters[glyph_index]; + break; + } + } + } + + state->glyph_index = glyph_index; + return TRUE; +} + +/* Split the glyph item at the start of the current cluster + */ +static PangoGlyphItem * +split_before_cluster_start (ApplyAttrsState *state) +{ + PangoGlyphItem *split_item; + int split_len = state->cluster_start - state->glyph_item->item->offset; + + split_item = pango_glyph_item_split (state->glyph_item, state->text, split_len); + append_attrs (split_item, state->segment_attrs); + + /* Adjust iteration to account for the split + */ + if (LTR (state->glyph_item)) + state->glyph_index -= split_item->glyphs->num_glyphs; + + return split_item; +} + /** * pango_glyph_item_apply_attrs: * @glyph_item: a shaped item @@ -144,6 +260,13 @@ append_attrs (PangoGlyphItem *glyph_item, * apply the shaping process and then reapply them to the result using * this function. * + * All attributes that start or end inside a cluster are applied + * to that cluster; for instance, if half of a cluster is underlined + * and the other-half strikethough, then the cluster will end + * up with both underline and strikethrough attributes. In these + * cases, it may happen that item->extra_attrs for some of the + * result items can have multiple attributes of the same type. + * * This function takes ownership of @glyph_item; it will be reused * as one of the elements in the list. * @@ -157,51 +280,125 @@ pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item, PangoAttrList *list) { PangoAttrIterator *iter = pango_attr_list_get_iterator (list); - PangoGlyphItem *new; GSList *result = NULL; - int start; - int end; + ApplyAttrsState state; + gboolean start_new_segment = FALSE; + int range_start, range_end; - gboolean ltr = (glyph_item->item->analysis.level % 2) == 0; + /* This routine works by iterating through the item cluster by + * cluster; we accumulate the attributes that we need to + * add to the next output item, and decide when to split + * off an output item based on two criteria: + * + * A) If cluster_start < attribute_start < cluster_end + * (attribute starts within cluster) then we need + * to split between the last cluster and this cluster. + * B) If cluster_start < attribute_end <= cluster_end, + * (attribute ends within cluster) then we need to + * split between this cluster and the next one. + */ + + state.glyph_item = glyph_item; + state.text = text; + + if (LTR (glyph_item)) + state.glyph_index = 0; + else + state.glyph_index = glyph_item->glyphs->num_glyphs - 1; + + state.cluster_end = glyph_item->item->offset; + + /* Advance the attr iterator to the start of the item + */ + while (TRUE) + { + pango_attr_iterator_range (iter, &range_start, &range_end); + if (range_end > state.cluster_end) + break; + + g_assert (pango_attr_iterator_next (iter)); + } + /* Short circuit the case when we don't actually need to + * split the item + */ + if (range_start <= glyph_item->item->offset && + range_end >= glyph_item->item->offset + glyph_item->item->length) + goto out; + + state.segment_attrs = pango_attr_iterator_get_attrs (iter); + while (TRUE) { - pango_attr_iterator_range (iter, &start, &end); + /* Find the next logical cluster; [range_start,range_end] + * is the first range that intersects the new cluster. + */ + if (!next_cluster (&state)) + break; - if (start > glyph_item->item->offset) + /* Split item into two, if this cluster isn't a continuation + * of the last cluster + */ + if (start_new_segment) { - if (start >= glyph_item->item->offset + glyph_item->item->length) - break; - - new = pango_glyph_item_split (glyph_item, text, - start - glyph_item->item->offset); - - result = g_slist_prepend (result, new); + result = g_slist_prepend (result, + split_before_cluster_start (&state)); + state.segment_attrs = pango_attr_iterator_get_attrs (iter); } + + start_new_segment = FALSE; - if (end > glyph_item->item->offset) + /* Loop over all ranges that intersect this cluster; exiting + * leaving [range_start,range_end] being the first range that + * intersects the next cluster. + */ + while (TRUE) { - if (end >= glyph_item->item->offset + glyph_item->item->length) + /* If any ranges end in this cluster, then the next cluster + * goes into a separate segment + */ + if (range_end <= state.cluster_end) + start_new_segment = TRUE; + + if (range_end > state.cluster_end) /* Range intersects next cluster */ + break; + + g_assert (pango_attr_iterator_next (iter)); + pango_attr_iterator_range (iter, &range_start, &range_end); + + if (range_start >= state.cluster_end) /* New range doesn't intersect this cluster */ { - append_attrs (glyph_item, pango_attr_iterator_get_attrs (iter)); + /* No gap between ranges, so previous range must of ended + * at cluster boundary. + */ + g_assert (range_start == state.cluster_end && start_new_segment); break; } - new = pango_glyph_item_split (glyph_item, text, - end - glyph_item->item->offset); - - append_attrs (new, pango_attr_iterator_get_attrs (iter)); - - result = g_slist_prepend (result, new); + /* If any ranges start *inside* this cluster, then we need + * to split the previous cluster into a separate segment + */ + if (range_start > state.cluster_start && + state.cluster_start != glyph_item->item->offset) + { + GSList *new_attrs = attr_slist_copy (state.segment_attrs); + result = g_slist_prepend (result, + split_before_cluster_start (&state)); + state.segment_attrs = new_attrs; + } + + state.segment_attrs = g_slist_concat (state.segment_attrs, + pango_attr_iterator_get_attrs (iter)); } - - if (!pango_attr_iterator_next (iter)) - break; } + out: + /* What's left in glyph_item is the remaining portion + */ + append_attrs (glyph_item, state.segment_attrs); result = g_slist_prepend (result, glyph_item); - if (ltr) + if (LTR (glyph_item)) result = g_slist_reverse (result); pango_attr_iterator_destroy (iter); |