/* Pango
* pango-glyph-item.c: Pair of PangoItem 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.h"
#include "pango-impl-utils.h"
#define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0)
/**
* pango_glyph_item_split:
* @orig: a #PangoItem
* @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).
*
* This function is similar in function to pango_item_split() (and uses
* it internally.)
*
* Return value: the newly allocated item representing text before
* @split_index, which should be freed
* with pango_glyph_item_free().
*
* Since: 1.2
**/
PangoGlyphItem *
pango_glyph_item_split (PangoGlyphItem *orig,
const char *text,
int split_index)
{
PangoGlyphItem *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 (PangoGlyphItem);
split_offset = g_utf8_pointer_to_offset (text + orig->item->offset,
text + orig->item->offset + split_index);
new->item = pango_item_split (orig->item, split_index, split_offset);
new->glyphs = pango_glyph_string_new ();
pango_glyph_string_set_size (new->glyphs, num_glyphs);
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));
memmove (orig->glyphs->glyphs, orig->glyphs->glyphs + num_glyphs,
num_remaining * sizeof (PangoGlyphInfo));
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 (PangoGlyphInfo));
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;
}
pango_glyph_string_set_size (orig->glyphs, orig->glyphs->num_glyphs - num_glyphs);
return new;
}
/**
* pango_glyph_item_copy:
* @orig: (nullable): a #PangoGlyphItem, may be %NULL
*
* Make a deep copy of an existing #PangoGlyphItem structure.
*
* Return value: (nullable): the newly allocated #PangoGlyphItem, which should
* be freed with pango_glyph_item_free(), or %NULL
* if @orig was %NULL.
*
* Since: 1.20
**/
PangoGlyphItem *
pango_glyph_item_copy (PangoGlyphItem *orig)
{
PangoGlyphItem *result;
if (orig == NULL)
return NULL;
result = g_slice_new (PangoGlyphItem);
result->item = pango_item_copy (orig->item);
result->glyphs = pango_glyph_string_copy (orig->glyphs);
return result;
}
/**
* pango_glyph_item_free:
* @glyph_item: (nullable): a #PangoGlyphItem, may be %NULL
*
* Frees a #PangoGlyphItem and resources to which it points.
*
* Since: 1.6
**/
void
pango_glyph_item_free (PangoGlyphItem *glyph_item)
{
if (glyph_item == NULL)
return;
if (glyph_item->item)
pango_item_free (glyph_item->item);
if (glyph_item->glyphs)
pango_glyph_string_free (glyph_item->glyphs);
g_slice_free (PangoGlyphItem, glyph_item);
}
G_DEFINE_BOXED_TYPE (PangoGlyphItem, pango_glyph_item,
pango_glyph_item_copy,
pango_glyph_item_free);
/**
* pango_glyph_item_iter_copy:
* @orig: (nullable): a #PangoGlyphItemIter, may be %NULL
*
* Make a shallow copy of an existing #PangoGlyphItemIter structure.
*
* Return value: (nullable): the newly allocated #PangoGlyphItemIter, which should
* be freed with pango_glyph_item_iter_free(), or %NULL
* if @orig was %NULL.
*
* Since: 1.22
**/
PangoGlyphItemIter *
pango_glyph_item_iter_copy (PangoGlyphItemIter *orig)
{
PangoGlyphItemIter *result;
if (orig == NULL)
return NULL;
result = g_slice_new (PangoGlyphItemIter);
*result = *orig;
return result;
}
/**
* pango_glyph_item_iter_free:
* @iter: (nullable): a #PangoGlyphItemIter, may be %NULL
*
* Frees a #PangoGlyphItemIter created by pango_glyph_item_iter_copy().
*
* Since: 1.22
**/
void
pango_glyph_item_iter_free (PangoGlyphItemIter *iter)
{
if (iter == NULL)
return;
g_slice_free (PangoGlyphItemIter, iter);
}
G_DEFINE_BOXED_TYPE (PangoGlyphItemIter, pango_glyph_item_iter,
pango_glyph_item_iter_copy,
pango_glyph_item_iter_free)
/**
* pango_glyph_item_iter_next_cluster:
* @iter: a #PangoGlyphItemIter
*
* Advances the iterator to the next cluster in the glyph item.
* See #PangoGlyphItemIter for details of cluster orders.
*
* Return value: %TRUE if the iterator was advanced, %FALSE if we were already on the
* last cluster.
*
* Since: 1.22
**/
gboolean
pango_glyph_item_iter_next_cluster (PangoGlyphItemIter *iter)
{
int glyph_index = iter->end_glyph;
PangoGlyphString *glyphs = iter->glyph_item->glyphs;
int cluster;
PangoItem *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 += pango_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 += pango_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;
}
/**
* pango_glyph_item_iter_prev_cluster:
* @iter: a #PangoGlyphItemIter
*
* Moves the iterator to the preceding cluster in the glyph item.
* See #PangoGlyphItemIter for details of cluster orders.
*
* Return value: %TRUE if the iterator was moved, %FALSE if we were already on the
* first cluster.
*
* Since: 1.22
**/
gboolean
pango_glyph_item_iter_prev_cluster (PangoGlyphItemIter *iter)
{
int glyph_index = iter->start_glyph;
PangoGlyphString *glyphs = iter->glyph_item->glyphs;
int cluster;
PangoItem *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 -= pango_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 -= pango_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;
}
/**
* pango_glyph_item_iter_init_start:
* @iter: a #PangoGlyphItemIter
* @glyph_item: the glyph item to iterate over
* @text: text corresponding to the glyph item
*
* Initializes a #PangoGlyphItemIter structure to point to the
* first cluster in a glyph item.
* See #PangoGlyphItemIter for details of cluster orders.
*
* Return value: %FALSE if there are no clusters in the glyph item
*
* Since: 1.22
**/
gboolean
pango_glyph_item_iter_init_start (PangoGlyphItemIter *iter,
PangoGlyphItem *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 pango_glyph_item_iter_next_cluster (iter);
}
/**
* pango_glyph_item_iter_init_end:
* @iter: a #PangoGlyphItemIter
* @glyph_item: the glyph item to iterate over
* @text: text corresponding to the glyph item
*
* Initializes a #PangoGlyphItemIter structure to point to the
* last cluster in a glyph item.
* See #PangoGlyphItemIter for details of cluster orders.
*
* Return value: %FALSE if there are no clusters in the glyph item
*
* Since: 1.22
**/
gboolean
pango_glyph_item_iter_init_end (PangoGlyphItemIter *iter,
PangoGlyphItem *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 pango_glyph_item_iter_prev_cluster (iter);
}
typedef struct
{
PangoGlyphItemIter iter;
GSList *segment_attrs;
} ApplyAttrsState;
/* Tack @attrs onto the attributes of glyph_item
*/
static void
append_attrs (PangoGlyphItem *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 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;
}
/* 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->iter.start_index - state->iter.glyph_item->item->offset;
split_item = pango_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;
}
/**
* pango_glyph_item_apply_attrs:
* @glyph_item: a shaped item
* @text: text that @list applies to
* @list: a #PangoAttrList
*
* Splits a shaped item (PangoGlyphItem) 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 (pango_attr_list_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 Pango.GlyphItem): a
* list of glyph items resulting from splitting @glyph_item. Free
* the elements using pango_glyph_item_free(), the list using
* g_slist_free().
*
* Since: 1.2
**/
GSList *
pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item,
const char *text,
PangoAttrList *list)
{
PangoAttrIterator *iter = pango_attr_list_get_iterator (list);
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
*/
do
{
pango_attr_iterator_range (iter, &range_start, &range_end);
if (range_end > glyph_item->item->offset)
break;
}
while (pango_attr_iterator_next (iter));
state.segment_attrs = pango_attr_iterator_get_attrs (iter);
is_ellipsis = (glyph_item->item->analysis.flags & PANGO_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 = pango_glyph_item_iter_init_start (&state.iter, glyph_item, text);
have_cluster;
have_cluster = pango_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 = pango_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 = pango_attr_iterator_next (iter);
pango_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,
pango_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);
pango_attr_iterator_destroy (iter);
return result;
}
/**
* pango_glyph_item_letter_space:
* @glyph_item: a #PangoGlyphItem
* @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 Pango 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.
*
* Since: 1.6
**/
void
pango_glyph_item_letter_space (PangoGlyphItem *glyph_item,
const char *text,
PangoLogAttr *log_attrs,
int letter_spacing)
{
PangoGlyphItemIter iter;
PangoGlyphInfo *glyphs = glyph_item->glyphs->glyphs;
gboolean have_cluster;
int space_left, space_right;
space_left = letter_spacing / 2;
/* hinting */
if ((letter_spacing & (PANGO_SCALE - 1)) == 0)
{
space_left = PANGO_UNITS_ROUND (space_left);
}
space_right = letter_spacing - space_left;
for (have_cluster = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
have_cluster;
have_cluster = pango_glyph_item_iter_next_cluster (&iter))
{
if (!log_attrs[iter.start_char].is_cursor_position)
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 ;
}
}
}
}
/**
* pango_glyph_item_get_logical_widths:
* @glyph_item: a #PangoGlyphItem
* @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 #PangoGlyphItem and the corresponding
* text, determine the screen 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 pango_glyph_string_get_logical_widths().
*
* Since: 1.26
**/
void
pango_glyph_item_get_logical_widths (PangoGlyphItem *glyph_item,
const char *text,
int *logical_widths)
{
PangoGlyphItemIter iter;
gboolean has_cluster;
int dir;
dir = glyph_item->item->analysis.level % 2 == 0 ? +1 : -1;
for (has_cluster = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
has_cluster;
has_cluster = pango_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);
}
}
}