summaryrefslogtreecommitdiff
path: root/pango2/pango-glyph-item.c
diff options
context:
space:
mode:
Diffstat (limited to 'pango2/pango-glyph-item.c')
-rw-r--r--pango2/pango-glyph-item.c837
1 files changed, 837 insertions, 0 deletions
diff --git a/pango2/pango-glyph-item.c b/pango2/pango-glyph-item.c
new file mode 100644
index 00000000..6ba75961
--- /dev/null
+++ b/pango2/pango-glyph-item.c
@@ -0,0 +1,837 @@
+/* Pango2
+ * pango-glyph-item.c: Pair of Pango2Item and a glyph string
+ *
+ * Copyright (C) 2002 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-glyph-item-private.h"
+#include "pango-glyph-iter-private.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+
+#define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0)
+
+/**
+ * pango2_glyph_item_split:
+ * @orig: a `Pango2Item`
+ * @text: text to which positions in @orig apply
+ * @split_index: byte index of position to split item, relative to the
+ * start of the item
+ *
+ * Modifies @orig to cover only the text after @split_index, and
+ * returns a new item that covers the text before @split_index that
+ * used to be in @orig.
+ *
+ * You can think of @split_index as the length of the returned item.
+ * @split_index may not be 0, and it may not be greater than or equal
+ * to the length of @orig (that is, there must be at least one byte
+ * assigned to each item, you can't create a zero-length item).
+ *
+ * Return value: the newly allocated item representing text before
+ * @split_index, which should be freed with [method@Pango2.GlyphItem.free]
+ */
+Pango2GlyphItem *
+pango2_glyph_item_split (Pango2GlyphItem *orig,
+ const char *text,
+ int split_index)
+{
+ Pango2GlyphItem *new;
+ int i;
+ int num_glyphs;
+ int num_remaining;
+ int split_offset;
+
+ 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 (orig))
+ {
+ for (i = 0; i < orig->glyphs->num_glyphs; i++)
+ {
+ if (orig->glyphs->log_clusters[i] >= split_index)
+ break;
+ }
+
+ if (i == orig->glyphs->num_glyphs) /* No splitting necessary */
+ return NULL;
+
+ split_index = orig->glyphs->log_clusters[i];
+ num_glyphs = i;
+ }
+ else
+ {
+ for (i = orig->glyphs->num_glyphs - 1; i >= 0; i--)
+ {
+ if (orig->glyphs->log_clusters[i] >= split_index)
+ break;
+ }
+
+ if (i < 0) /* No splitting necessary */
+ return NULL;
+
+ split_index = orig->glyphs->log_clusters[i];
+ num_glyphs = orig->glyphs->num_glyphs - 1 - i;
+ }
+
+ num_remaining = orig->glyphs->num_glyphs - num_glyphs;
+
+ new = g_slice_new (Pango2GlyphItem);
+ split_offset = g_utf8_pointer_to_offset (text + orig->item->offset,
+ text + orig->item->offset + split_index);
+ new->item = pango2_item_split (orig->item, split_index, split_offset);
+
+ new->glyphs = pango2_glyph_string_new ();
+ pango2_glyph_string_set_size (new->glyphs, num_glyphs);
+
+ if (LTR (orig))
+ {
+ memcpy (new->glyphs->glyphs, orig->glyphs->glyphs, num_glyphs * sizeof (Pango2GlyphInfo));
+ memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters, num_glyphs * sizeof (int));
+
+ memmove (orig->glyphs->glyphs, orig->glyphs->glyphs + num_glyphs,
+ num_remaining * sizeof (Pango2GlyphInfo));
+ for (i = num_glyphs; i < orig->glyphs->num_glyphs; i++)
+ orig->glyphs->log_clusters[i - num_glyphs] = orig->glyphs->log_clusters[i] - split_index;
+ }
+ else
+ {
+ memcpy (new->glyphs->glyphs, orig->glyphs->glyphs + num_remaining, num_glyphs * sizeof (Pango2GlyphInfo));
+ memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters + num_remaining, num_glyphs * sizeof (int));
+
+ for (i = 0; i < num_remaining; i++)
+ orig->glyphs->log_clusters[i] = orig->glyphs->log_clusters[i] - split_index;
+ }
+
+ pango2_glyph_string_set_size (orig->glyphs, orig->glyphs->num_glyphs - num_glyphs);
+
+ new->y_offset = orig->y_offset;
+ new->start_x_offset = orig->start_x_offset;
+ new->end_x_offset = -orig->start_x_offset;
+
+ return new;
+}
+
+/**
+ * pango2_glyph_item_copy:
+ * @orig: (nullable): a `Pango2GlyphItem`
+ *
+ * Make a deep copy of an existing `Pango2GlyphItem` structure.
+ *
+ * Return value: (nullable): the newly allocated `Pango2GlyphItem`
+ */
+Pango2GlyphItem *
+pango2_glyph_item_copy (Pango2GlyphItem *orig)
+{
+ Pango2GlyphItem *result;
+
+ if (orig == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2GlyphItem);
+
+ result->item = pango2_item_copy (orig->item);
+ result->glyphs = pango2_glyph_string_copy (orig->glyphs);
+ result->y_offset = orig->y_offset;
+ result->start_x_offset = orig->start_x_offset;
+ result->end_x_offset = orig->end_x_offset;
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_free:
+ * @glyph_item: (nullable): a `Pango2GlyphItem`
+ *
+ * Frees a `Pango2GlyphItem` and resources to which it points.
+ */
+void
+pango2_glyph_item_free (Pango2GlyphItem *glyph_item)
+{
+ if (glyph_item == NULL)
+ return;
+
+ if (glyph_item->item)
+ pango2_item_free (glyph_item->item);
+ if (glyph_item->glyphs)
+ pango2_glyph_string_free (glyph_item->glyphs);
+
+ g_slice_free (Pango2GlyphItem, glyph_item);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2GlyphItem, pango2_glyph_item,
+ pango2_glyph_item_copy,
+ pango2_glyph_item_free);
+
+
+/**
+ * pango2_glyph_item_iter_copy:
+ * @orig: (nullable): a `Pango2GlyphItem`Iter
+ *
+ * Make a shallow copy of an existing `Pango2GlyphItemIter` structure.
+ *
+ * Return value: (nullable): the newly allocated `Pango2GlyphItemIter`
+ */
+Pango2GlyphItemIter *
+pango2_glyph_item_iter_copy (Pango2GlyphItemIter *orig)
+{
+ Pango2GlyphItemIter *result;
+
+ if (orig == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2GlyphItemIter);
+
+ *result = *orig;
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_iter_free:
+ * @iter: (nullable): a `Pango2GlyphItemIter`
+ *
+ * Frees a `Pango2GlyphItemIter`.
+ */
+void
+pango2_glyph_item_iter_free (Pango2GlyphItemIter *iter)
+{
+ if (iter == NULL)
+ return;
+
+ g_slice_free (Pango2GlyphItemIter, iter);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2GlyphItemIter, pango2_glyph_item_iter,
+ pango2_glyph_item_iter_copy,
+ pango2_glyph_item_iter_free)
+
+/**
+ * pango2_glyph_item_iter_next_cluster:
+ * @iter: a `Pango2GlyphItemIter`
+ *
+ * Advances the iterator to the next cluster in the glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %TRUE if the iterator was advanced,
+ * %FALSE if we were already on the last cluster.
+ */
+gboolean
+pango2_glyph_item_iter_next_cluster (Pango2GlyphItemIter *iter)
+{
+ int glyph_index = iter->end_glyph;
+ Pango2GlyphString *glyphs = iter->glyph_item->glyphs;
+ int cluster;
+ Pango2Item *item = iter->glyph_item->item;
+
+ if (LTR (iter->glyph_item))
+ {
+ if (glyph_index == glyphs->num_glyphs)
+ return FALSE;
+ }
+ else
+ {
+ if (glyph_index < 0)
+ return FALSE;
+ }
+
+ iter->start_glyph = iter->end_glyph;
+ iter->start_index = iter->end_index;
+ iter->start_char = iter->end_char;
+
+ if (LTR (iter->glyph_item))
+ {
+ cluster = glyphs->log_clusters[glyph_index];
+ while (TRUE)
+ {
+ glyph_index++;
+
+ if (glyph_index == glyphs->num_glyphs)
+ {
+ iter->end_index = item->offset + item->length;
+ iter->end_char = item->num_chars;
+ break;
+ }
+
+ if (glyphs->log_clusters[glyph_index] > cluster)
+ {
+ iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->end_char += pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+ else /* RTL */
+ {
+ cluster = glyphs->log_clusters[glyph_index];
+ while (TRUE)
+ {
+ glyph_index--;
+
+ if (glyph_index < 0)
+ {
+ iter->end_index = item->offset + item->length;
+ iter->end_char = item->num_chars;
+ break;
+ }
+
+ if (glyphs->log_clusters[glyph_index] > cluster)
+ {
+ iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->end_char += pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+
+ iter->end_glyph = glyph_index;
+
+ g_assert (iter->start_char <= iter->end_char);
+ g_assert (iter->end_char <= item->num_chars);
+
+ return TRUE;
+}
+
+/**
+ * pango2_glyph_item_iter_prev_cluster:
+ * @iter: a `Pango2GlyphItemIter`
+ *
+ * Moves the iterator to the preceding cluster in the glyph item.
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %TRUE if the iterator was moved,
+ * %FALSE if we were already on the first cluster.
+ */
+gboolean
+pango2_glyph_item_iter_prev_cluster (Pango2GlyphItemIter *iter)
+{
+ int glyph_index = iter->start_glyph;
+ Pango2GlyphString *glyphs = iter->glyph_item->glyphs;
+ int cluster;
+ Pango2Item *item = iter->glyph_item->item;
+
+ if (LTR (iter->glyph_item))
+ {
+ if (glyph_index == 0)
+ return FALSE;
+ }
+ else
+ {
+ if (glyph_index == glyphs->num_glyphs - 1)
+ return FALSE;
+
+ }
+
+ iter->end_glyph = iter->start_glyph;
+ iter->end_index = iter->start_index;
+ iter->end_char = iter->start_char;
+
+ if (LTR (iter->glyph_item))
+ {
+ cluster = glyphs->log_clusters[glyph_index - 1];
+ while (TRUE)
+ {
+ if (glyph_index == 0)
+ {
+ iter->start_index = item->offset;
+ iter->start_char = 0;
+ break;
+ }
+
+ glyph_index--;
+
+ if (glyphs->log_clusters[glyph_index] < cluster)
+ {
+ glyph_index++;
+ iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->start_char -= pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+ else /* RTL */
+ {
+ cluster = glyphs->log_clusters[glyph_index + 1];
+ while (TRUE)
+ {
+ if (glyph_index == glyphs->num_glyphs - 1)
+ {
+ iter->start_index = item->offset;
+ iter->start_char = 0;
+ break;
+ }
+
+ glyph_index++;
+
+ if (glyphs->log_clusters[glyph_index] < cluster)
+ {
+ glyph_index--;
+ iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->start_char -= pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+
+ iter->start_glyph = glyph_index;
+
+ g_assert (iter->start_char <= iter->end_char);
+ g_assert (0 <= iter->start_char);
+
+ return TRUE;
+}
+
+/**
+ * pango2_glyph_item_iter_init_start:
+ * @iter: a `Pango2GlyphItemIter`
+ * @glyph_item: the glyph item to iterate over
+ * @text: text corresponding to the glyph item
+ *
+ * Initializes a `Pango2GlyphItemIter` structure to point to the
+ * first cluster in a glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %FALSE if there are no clusters in the glyph item
+ */
+gboolean
+pango2_glyph_item_iter_init_start (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text)
+{
+ iter->glyph_item = glyph_item;
+ iter->text = text;
+
+ if (LTR (glyph_item))
+ iter->end_glyph = 0;
+ else
+ iter->end_glyph = glyph_item->glyphs->num_glyphs - 1;
+
+ iter->end_index = glyph_item->item->offset;
+ iter->end_char = 0;
+
+ iter->start_glyph = iter->end_glyph;
+ iter->start_index = iter->end_index;
+ iter->start_char = iter->end_char;
+
+ /* Advance onto the first cluster of the glyph item */
+ return pango2_glyph_item_iter_next_cluster (iter);
+}
+
+/**
+ * pango2_glyph_item_iter_init_end:
+ * @iter: a `Pango2GlyphItemIter`
+ * @glyph_item: the glyph item to iterate over
+ * @text: text corresponding to the glyph item
+ *
+ * Initializes a `Pango2GlyphItemIter` structure to point to the
+ * last cluster in a glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %FALSE if there are no clusters in the glyph item
+ */
+gboolean
+pango2_glyph_item_iter_init_end (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text)
+{
+ iter->glyph_item = glyph_item;
+ iter->text = text;
+
+ if (LTR (glyph_item))
+ iter->start_glyph = glyph_item->glyphs->num_glyphs;
+ else
+ iter->start_glyph = -1;
+
+ iter->start_index = glyph_item->item->offset + glyph_item->item->length;
+ iter->start_char = glyph_item->item->num_chars;
+
+ iter->end_glyph = iter->start_glyph;
+ iter->end_index = iter->start_index;
+ iter->end_char = iter->start_char;
+
+ /* Advance onto the first cluster of the glyph item */
+ return pango2_glyph_item_iter_prev_cluster (iter);
+}
+
+typedef struct
+{
+ Pango2GlyphItemIter iter;
+
+ GSList *segment_attrs;
+} ApplyAttrsState;
+
+/* Tack @attrs onto the attributes of glyph_item
+ */
+static void
+append_attrs (Pango2GlyphItem *glyph_item,
+ GSList *attrs)
+{
+ glyph_item->item->analysis.extra_attrs =
+ g_slist_concat (glyph_item->item->analysis.extra_attrs, attrs);
+}
+
+/* Make a deep copy of a GSList of Pango2Attribute
+ */
+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 = pango2_attribute_copy (tmp_list->data);
+
+ return new_attrs;
+}
+
+/* Split the glyph item at the start of the current cluster
+ */
+static Pango2GlyphItem *
+split_before_cluster_start (ApplyAttrsState *state)
+{
+ Pango2GlyphItem *split_item;
+ int split_len = state->iter.start_index - state->iter.glyph_item->item->offset;
+
+ split_item = pango2_glyph_item_split (state->iter.glyph_item, state->iter.text, split_len);
+ append_attrs (split_item, state->segment_attrs);
+
+ /* Adjust iteration to account for the split
+ */
+ if (LTR (state->iter.glyph_item))
+ {
+ state->iter.start_glyph -= split_item->glyphs->num_glyphs;
+ state->iter.end_glyph -= split_item->glyphs->num_glyphs;
+ }
+
+ state->iter.start_char -= split_item->item->num_chars;
+ state->iter.end_char -= split_item->item->num_chars;
+
+ return split_item;
+}
+
+/**
+ * pango2_glyph_item_apply_attrs:
+ * @glyph_item: a shaped item
+ * @text: text that @list applies to
+ * @list: a `Pango2AttrList`
+ *
+ * Splits a shaped item (`Pango2GlyphItem`) into multiple items based
+ * on an attribute list.
+ *
+ * The idea is that if you have attributes that don't affect shaping,
+ * such as color or underline, to avoid affecting shaping, you filter
+ * them out ([method@Pango2.AttrList.filter]), 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 strikethrough, 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.
+ *
+ * Return value: (transfer full) (element-type Pango2.GlyphItem): a
+ * list of glyph items resulting from splitting @glyph_item. Free
+ * the elements using [method@Pango2.GlyphItem.free], the list using
+ * [func@GLib.SList.free]
+ */
+GSList *
+pango2_glyph_item_apply_attrs (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2AttrList *list)
+{
+ Pango2AttrIterator iter;
+ GSList *result = NULL;
+ ApplyAttrsState state;
+ gboolean start_new_segment = FALSE;
+ gboolean have_cluster;
+ int range_start, range_end;
+ gboolean is_ellipsis;
+
+ /* 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 start_index < attribute_start < end_index
+ * (attribute starts within cluster) then we need
+ * to split between the last cluster and this cluster.
+ * B) If start_index < attribute_end <= end_index,
+ * (attribute ends within cluster) then we need to
+ * split between this cluster and the next one.
+ */
+
+ /* Advance the attr iterator to the start of the item
+ */
+ pango2_attr_list_init_iterator (list, &iter);
+ do
+ {
+ pango2_attr_iterator_range (&iter, &range_start, &range_end);
+ if (range_end > glyph_item->item->offset)
+ break;
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ state.segment_attrs = pango2_attr_iterator_get_attrs (&iter);
+
+ is_ellipsis = (glyph_item->item->analysis.flags & PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS) != 0;
+
+ /* Short circuit the case when we don't actually need to
+ * split the item
+ */
+ if (is_ellipsis ||
+ (range_start <= glyph_item->item->offset &&
+ range_end >= glyph_item->item->offset + glyph_item->item->length))
+ goto out;
+
+ for (have_cluster = pango2_glyph_item_iter_init_start (&state.iter, glyph_item, text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (&state.iter))
+ {
+ gboolean have_next;
+
+ /* [range_start,range_end] is the first range that intersects
+ * the current cluster.
+ */
+
+ /* Split item into two, if this cluster isn't a continuation
+ * of the last cluster
+ */
+ if (start_new_segment)
+ {
+ result = g_slist_prepend (result,
+ split_before_cluster_start (&state));
+ state.segment_attrs = pango2_attr_iterator_get_attrs (&iter);
+ }
+
+ start_new_segment = FALSE;
+
+ /* Loop over all ranges that intersect this cluster; exiting
+ * leaving [range_start,range_end] being the first range that
+ * intersects the next cluster.
+ */
+ do
+ {
+ if (range_end > state.iter.end_index) /* Range intersects next cluster */
+ break;
+
+ /* Since ranges end in this cluster, the next cluster goes into a
+ * separate segment
+ */
+ start_new_segment = TRUE;
+
+ have_next = pango2_attr_iterator_next (&iter);
+ pango2_attr_iterator_range (&iter, &range_start, &range_end);
+
+ if (range_start >= state.iter.end_index) /* New range doesn't intersect this cluster */
+ {
+ /* No gap between ranges, so previous range must of ended
+ * at cluster boundary.
+ */
+ g_assert (range_start == state.iter.end_index && start_new_segment);
+ break;
+ }
+
+ /* If any ranges start *inside* this cluster, then we need
+ * to split the previous cluster into a separate segment
+ */
+ if (range_start > state.iter.start_index &&
+ state.iter.start_index != 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,
+ pango2_attr_iterator_get_attrs (&iter));
+ }
+ while (have_next);
+ }
+
+ 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 (glyph_item))
+ result = g_slist_reverse (result);
+
+ pango2_attr_iterator_clear (&iter);
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_letter_space:
+ * @glyph_item: a `Pango2GlyphItem`
+ * @text: text that @glyph_item corresponds to
+ * (glyph_item->item->offset is an offset from the
+ * start of @text)
+ * @log_attrs: (array): logical attributes for the item
+ * (the first logical attribute refers to the position
+ * before the first character in the item)
+ * @letter_spacing: amount of letter spacing to add
+ * in Pango2 units. May be negative, though too large
+ * negative values will give ugly results.
+ *
+ * Adds spacing between the graphemes of @glyph_item to
+ * give the effect of typographic letter spacing.
+ */
+void
+pango2_glyph_item_letter_space (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ int letter_spacing)
+{
+ Pango2GlyphItemIter iter;
+ Pango2GlyphInfo *glyphs = glyph_item->glyphs->glyphs;
+ gboolean have_cluster;
+ int space_left, space_right;
+
+ space_left = letter_spacing / 2;
+
+ /* hinting */
+ if ((letter_spacing & (PANGO2_SCALE - 1)) == 0)
+ {
+ space_left = PANGO2_UNITS_ROUND (space_left);
+ }
+
+ space_right = letter_spacing - space_left;
+
+ for (have_cluster = pango2_glyph_item_iter_init_start (&iter, glyph_item, text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (&iter))
+ {
+ if (!log_attrs[iter.start_char].is_cursor_position)
+ {
+ if (glyphs[iter.start_glyph].geometry.width == 0)
+ {
+ if (iter.start_glyph < iter.end_glyph) /* LTR */
+ glyphs[iter.start_glyph].geometry.x_offset -= space_right;
+ else
+ glyphs[iter.start_glyph].geometry.x_offset += space_left;
+ }
+ continue;
+ }
+
+ if (iter.start_glyph < iter.end_glyph) /* LTR */
+ {
+ if (iter.start_char > 0)
+ {
+ glyphs[iter.start_glyph].geometry.width += space_left ;
+ glyphs[iter.start_glyph].geometry.x_offset += space_left ;
+ }
+ if (iter.end_char < glyph_item->item->num_chars)
+ {
+ glyphs[iter.end_glyph-1].geometry.width += space_right;
+ }
+ }
+ else /* RTL */
+ {
+ if (iter.start_char > 0)
+ {
+ glyphs[iter.start_glyph].geometry.width += space_right;
+ }
+ if (iter.end_char < glyph_item->item->num_chars)
+ {
+ glyphs[iter.end_glyph+1].geometry.x_offset += space_left ;
+ glyphs[iter.end_glyph+1].geometry.width += space_left ;
+ }
+ }
+ }
+}
+
+/**
+ * pango2_glyph_item_get_logical_widths:
+ * @glyph_item: a `Pango2GlyphItem`
+ * @text: text that @glyph_item corresponds to
+ * (glyph_item->item->offset is an offset from the
+ * start of @text)
+ * @logical_widths: (array): an array whose length is the number of
+ * characters in glyph_item (equal to glyph_item->item->num_chars)
+ * to be filled in with the resulting character widths.
+ *
+ * Given a `Pango2GlyphItem` and the corresponding text, determine the
+ * width corresponding to each character.
+ *
+ * When multiple characters compose a single cluster, the width of the
+ * entire cluster is divided equally among the characters.
+ *
+ * See also [method@Pango2.GlyphString.get_logical_widths].
+ */
+void
+pango2_glyph_item_get_logical_widths (Pango2GlyphItem *glyph_item,
+ const char *text,
+ int *logical_widths)
+{
+ Pango2GlyphItemIter iter;
+ gboolean has_cluster;
+ int dir;
+
+ dir = glyph_item->item->analysis.level % 2 == 0 ? +1 : -1;
+ for (has_cluster = pango2_glyph_item_iter_init_start (&iter, glyph_item, text);
+ has_cluster;
+ has_cluster = pango2_glyph_item_iter_next_cluster (&iter))
+ {
+ int glyph_index, char_index, num_chars, cluster_width = 0, char_width;
+
+ for (glyph_index = iter.start_glyph;
+ glyph_index != iter.end_glyph;
+ glyph_index += dir)
+ {
+ cluster_width += glyph_item->glyphs->glyphs[glyph_index].geometry.width;
+ }
+
+ num_chars = iter.end_char - iter.start_char;
+ if (num_chars) /* pedantic */
+ {
+ char_width = cluster_width / num_chars;
+
+ for (char_index = iter.start_char;
+ char_index < iter.end_char;
+ char_index++)
+ {
+ logical_widths[char_index] = char_width;
+ }
+
+ /* add any residues to the first char */
+ logical_widths[iter.start_char] += cluster_width - (char_width * num_chars);
+ }
+ }
+}