2 * pango-glyph-item.c: Pair of PangoItem and a glyph string
4 * Copyright (C) 2002 Red Hat Software
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
25 #include "pango-glyph-item.h"
26 #include "pango-impl-utils.h"
28 #define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0)
31 * pango_glyph_item_split:
33 * @text: text to which positions in @orig apply
34 * @split_index: byte index of position to split item, relative to the start of the item
36 * Modifies @orig to cover only the text after @split_index, and
37 * returns a new item that covers the text before @split_index that
38 * used to be in @orig. You can think of @split_index as the length of
39 * the returned item. @split_index may not be 0, and it may not be
40 * greater than or equal to the length of @orig (that is, there must
41 * be at least one byte assigned to each item, you can't create a
44 * This function is similar in function to pango_item_split() (and uses
47 * Return value: the newly allocated item representing text before
48 * @split_index, which should be freed
49 * with pango_glyph_item_free().
54 pango_glyph_item_split (PangoGlyphItem *orig,
64 g_return_val_if_fail (orig != NULL, NULL);
65 g_return_val_if_fail (orig->item->length > 0, NULL);
66 g_return_val_if_fail (split_index > 0, NULL);
67 g_return_val_if_fail (split_index < orig->item->length, NULL);
71 for (i = 0; i < orig->glyphs->num_glyphs; i++)
73 if (orig->glyphs->log_clusters[i] >= split_index)
77 if (i == orig->glyphs->num_glyphs) /* No splitting necessary */
80 split_index = orig->glyphs->log_clusters[i];
85 for (i = orig->glyphs->num_glyphs - 1; i >= 0; i--)
87 if (orig->glyphs->log_clusters[i] >= split_index)
91 if (i < 0) /* No splitting necessary */
94 split_index = orig->glyphs->log_clusters[i];
95 num_glyphs = orig->glyphs->num_glyphs - 1 - i;
98 num_remaining = orig->glyphs->num_glyphs - num_glyphs;
100 new = g_slice_new (PangoGlyphItem);
101 split_offset = g_utf8_pointer_to_offset (text + orig->item->offset,
102 text + orig->item->offset + split_index);
103 new->item = pango_item_split (orig->item, split_index, split_offset);
105 new->glyphs = pango_glyph_string_new ();
106 pango_glyph_string_set_size (new->glyphs, num_glyphs);
110 memcpy (new->glyphs->glyphs, orig->glyphs->glyphs, num_glyphs * sizeof (PangoGlyphInfo));
111 memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters, num_glyphs * sizeof (int));
113 memmove (orig->glyphs->glyphs, orig->glyphs->glyphs + num_glyphs,
114 num_remaining * sizeof (PangoGlyphInfo));
115 for (i = num_glyphs; i < orig->glyphs->num_glyphs; i++)
116 orig->glyphs->log_clusters[i - num_glyphs] = orig->glyphs->log_clusters[i] - split_index;
120 memcpy (new->glyphs->glyphs, orig->glyphs->glyphs + num_remaining, num_glyphs * sizeof (PangoGlyphInfo));
121 memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters + num_remaining, num_glyphs * sizeof (int));
123 for (i = 0; i < num_remaining; i++)
124 orig->glyphs->log_clusters[i] = orig->glyphs->log_clusters[i] - split_index;
127 pango_glyph_string_set_size (orig->glyphs, orig->glyphs->num_glyphs - num_glyphs);
133 * pango_glyph_item_copy:
134 * @orig: a #PangoGlyphItem, may be %NULL
136 * Make a deep copy of an existing #PangoGlyphItem structure.
138 * Return value: the newly allocated #PangoGlyphItem, which should
139 * be freed with pango_glyph_item_free(), or %NULL
140 * if @orig was %NULL.
145 pango_glyph_item_copy (PangoGlyphItem *orig)
147 PangoGlyphItem *result;
152 result = g_slice_new (PangoGlyphItem);
154 result->item = pango_item_copy (orig->item);
155 result->glyphs = pango_glyph_string_copy (orig->glyphs);
161 * pango_glyph_item_free:
162 * @glyph_item: a #PangoGlyphItem, may be %NULL
164 * Frees a #PangoGlyphItem and resources to which it points.
169 pango_glyph_item_free (PangoGlyphItem *glyph_item)
171 if (glyph_item == NULL)
174 if (glyph_item->item)
175 pango_item_free (glyph_item->item);
176 if (glyph_item->glyphs)
177 pango_glyph_string_free (glyph_item->glyphs);
179 g_slice_free (PangoGlyphItem, glyph_item);
183 pango_glyph_item_get_type (void)
185 static GType our_type = 0;
187 if (G_UNLIKELY (our_type == 0))
188 our_type = g_boxed_type_register_static (I_("PangoGlyphItem"),
189 (GBoxedCopyFunc) pango_glyph_item_copy,
190 (GBoxedFreeFunc) pango_glyph_item_free);
196 * pango_glyph_item_iter_copy:
197 * @orig: a #PangoGlyphItemIter, may be %NULL
199 * Make a shallow copy of an existing #PangoGlyphItemIter structure.
201 * Return value: the newly allocated #PangoGlyphItemIter, which should
202 * be freed with pango_glyph_item_iter_free(), or %NULL
203 * if @orig was %NULL.
208 pango_glyph_item_iter_copy (PangoGlyphItemIter *orig)
210 PangoGlyphItemIter *result;
215 result = g_slice_new (PangoGlyphItemIter);
223 * pango_glyph_item_iter_free:
224 * @iter: a #PangoGlyphItemIter, may be %NULL
226 * Frees a #PangoGlyphItemIter created by pango_glyph_item_iter_copy().
231 pango_glyph_item_iter_free (PangoGlyphItemIter *iter)
236 g_slice_free (PangoGlyphItemIter, iter);
240 pango_glyph_item_iter_get_type (void)
242 static GType our_type = 0;
244 if (G_UNLIKELY (our_type == 0))
245 our_type = g_boxed_type_register_static (I_("PangoGlyphItemIter"),
246 (GBoxedCopyFunc) pango_glyph_item_iter_copy,
247 (GBoxedFreeFunc) pango_glyph_item_iter_free);
252 * pango_glyph_item_iter_next_cluster:
253 * @iter: a #PangoGlyphItemIter
255 * Advances the iterator to the next cluster in the glyph item.
256 * See #PangoGlyphItemIter for details of cluster orders.
258 * Return value: %TRUE if the iterator was advanced, %FALSE if we were already on the
264 pango_glyph_item_iter_next_cluster (PangoGlyphItemIter *iter)
266 int glyph_index = iter->end_glyph;
267 PangoGlyphString *glyphs = iter->glyph_item->glyphs;
269 PangoItem *item = iter->glyph_item->item;
271 if (LTR (iter->glyph_item))
273 if (glyph_index == glyphs->num_glyphs)
282 iter->start_glyph = iter->end_glyph;
283 iter->start_index = iter->end_index;
284 iter->start_char = iter->end_char;
286 if (LTR (iter->glyph_item))
288 cluster = glyphs->log_clusters[glyph_index];
293 if (glyph_index == glyphs->num_glyphs)
295 iter->end_index = item->offset + item->length;
296 iter->end_char = item->num_chars;
300 if (glyphs->log_clusters[glyph_index] != cluster)
302 iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
303 iter->end_char += pango_utf8_strlen (iter->text + iter->start_index,
304 iter->end_index - iter->start_index);
311 cluster = glyphs->log_clusters[glyph_index];
318 iter->end_index = item->offset + item->length;
319 iter->end_char = item->num_chars;
323 if (glyphs->log_clusters[glyph_index] != cluster)
325 iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
326 iter->end_char += pango_utf8_strlen (iter->text + iter->start_index,
327 iter->end_index - iter->start_index);
333 iter->end_glyph = glyph_index;
338 * pango_glyph_item_iter_prev_cluster:
339 * @iter: a #PangoGlyphItemIter
341 * Moves the iterator to the preceding cluster in the glyph item.
342 * See #PangoGlyphItemIter for details of cluster orders.
344 * Return value: %TRUE if the iterator was moved, %FALSE if we were already on the
350 pango_glyph_item_iter_prev_cluster (PangoGlyphItemIter *iter)
352 int glyph_index = iter->start_glyph;
353 PangoGlyphString *glyphs = iter->glyph_item->glyphs;
355 PangoItem *item = iter->glyph_item->item;
357 if (LTR (iter->glyph_item))
359 if (glyph_index == 0)
364 if (glyph_index == glyphs->num_glyphs - 1)
369 iter->end_glyph = iter->start_glyph;
370 iter->end_index = iter->start_index;
371 iter->end_char = iter->start_char;
373 if (LTR (iter->glyph_item))
375 cluster = glyphs->log_clusters[glyph_index - 1];
380 if (glyph_index == 0)
382 iter->start_index = item->offset;
383 iter->start_char = 0;
387 if (glyphs->log_clusters[glyph_index] != cluster)
390 iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
391 iter->start_char -= pango_utf8_strlen (iter->text + iter->start_index,
392 iter->end_index - iter->start_index);
399 cluster = glyphs->log_clusters[glyph_index + 1];
404 if (glyph_index == glyphs->num_glyphs - 1)
406 iter->start_index = item->offset;
407 iter->start_char = 0;
411 if (glyphs->log_clusters[glyph_index] != cluster)
414 iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
415 iter->start_char -= pango_utf8_strlen (iter->text + iter->start_index,
416 iter->end_index - iter->start_index);
422 iter->start_glyph = glyph_index;
427 * pango_glyph_item_iter_init_start:
428 * @iter: a #PangoGlyphItemIter
429 * @glyph_item: the glyph item to iterate over
430 * @text: text corresponding to the glyph item
432 * Initializes a #PangoGlyphItemIter structure to point to the
433 * first cluster in a glyph item.
434 * See #PangoGlyphItemIter for details of cluster orders.
436 * Return value: %FALSE if there are no clusters in the glyph item
441 pango_glyph_item_iter_init_start (PangoGlyphItemIter *iter,
442 PangoGlyphItem *glyph_item,
445 iter->glyph_item = glyph_item;
448 if (LTR (glyph_item))
451 iter->end_glyph = glyph_item->glyphs->num_glyphs - 1;
453 iter->end_index = glyph_item->item->offset;
456 iter->start_glyph = iter->end_glyph;
457 iter->start_index = iter->end_index;
458 iter->start_char = iter->end_char;
460 /* Advance onto the first cluster of the glyph item */
461 return pango_glyph_item_iter_next_cluster (iter);
465 * pango_glyph_item_iter_init_end:
466 * @iter: a #PangoGlyphItemIter
467 * @glyph_item: the glyph item to iterate over
468 * @text: text corresponding to the glyph item
470 * Initializes a #PangoGlyphItemIter structure to point to the
471 * last cluster in a glyph item.
472 * See #PangoGlyphItemIter for details of cluster orders.
474 * Return value: %FALSE if there are no clusters in the glyph item
479 pango_glyph_item_iter_init_end (PangoGlyphItemIter *iter,
480 PangoGlyphItem *glyph_item,
483 iter->glyph_item = glyph_item;
486 if (LTR (glyph_item))
487 iter->start_glyph = glyph_item->glyphs->num_glyphs;
489 iter->start_glyph = -1;
491 iter->start_index = glyph_item->item->offset + glyph_item->item->length;
492 iter->start_char = glyph_item->item->num_chars;
494 iter->end_glyph = iter->start_glyph;
495 iter->end_index = iter->start_index;
496 iter->end_char = iter->start_char;
498 /* Advance onto the first cluster of the glyph item */
499 return pango_glyph_item_iter_prev_cluster (iter);
504 PangoGlyphItemIter iter;
506 GSList *segment_attrs;
509 /* Tack @attrs onto the attributes of glyph_item
512 append_attrs (PangoGlyphItem *glyph_item,
515 glyph_item->item->analysis.extra_attrs =
516 g_slist_concat (glyph_item->item->analysis.extra_attrs, attrs);
519 /* Make a deep copy of a #GSList of PangoAttribute
522 attr_slist_copy (GSList *attrs)
527 new_attrs = g_slist_copy (attrs);
529 for (tmp_list = new_attrs; tmp_list; tmp_list = tmp_list->next)
530 tmp_list->data = pango_attribute_copy (tmp_list->data);
535 /* Split the glyph item at the start of the current cluster
537 static PangoGlyphItem *
538 split_before_cluster_start (ApplyAttrsState *state)
540 PangoGlyphItem *split_item;
541 int split_len = state->iter.start_index - state->iter.glyph_item->item->offset;
543 split_item = pango_glyph_item_split (state->iter.glyph_item, state->iter.text, split_len);
544 append_attrs (split_item, state->segment_attrs);
546 /* Adjust iteration to account for the split
548 if (LTR (state->iter.glyph_item))
550 state->iter.start_glyph -= split_item->glyphs->num_glyphs;
551 state->iter.end_glyph -= split_item->glyphs->num_glyphs;
554 state->iter.start_char -= split_item->item->num_chars;
555 state->iter.end_char -= split_item->item->num_chars;
561 * pango_glyph_item_apply_attrs:
562 * @glyph_item: a shaped item
563 * @text: text that @list applies to
564 * @list: a #PangoAttrList
566 * Splits a shaped item (PangoGlyphItem) into multiple items based
567 * on an attribute list. The idea is that if you have attributes
568 * that don't affect shaping, such as color or underline, to avoid
569 * affecting shaping, you filter them out (pango_attr_list_filter()),
570 * apply the shaping process and then reapply them to the result using
573 * All attributes that start or end inside a cluster are applied
574 * to that cluster; for instance, if half of a cluster is underlined
575 * and the other-half strikethrough, then the cluster will end
576 * up with both underline and strikethrough attributes. In these
577 * cases, it may happen that item->extra_attrs for some of the
578 * result items can have multiple attributes of the same type.
580 * This function takes ownership of @glyph_item; it will be reused
581 * as one of the elements in the list.
583 * Return value: a list of glyph items resulting from splitting
584 * @glyph_item. Free the elements using pango_glyph_item_free(),
585 * the list using g_slist_free().
590 pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item,
594 PangoAttrIterator *iter = pango_attr_list_get_iterator (list);
595 GSList *result = NULL;
596 ApplyAttrsState state;
597 gboolean start_new_segment = FALSE;
598 gboolean have_cluster;
599 int range_start, range_end;
601 /* This routine works by iterating through the item cluster by
602 * cluster; we accumulate the attributes that we need to
603 * add to the next output item, and decide when to split
604 * off an output item based on two criteria:
606 * A) If start_index < attribute_start < end_index
607 * (attribute starts within cluster) then we need
608 * to split between the last cluster and this cluster.
609 * B) If start_index < attribute_end <= end_index,
610 * (attribute ends within cluster) then we need to
611 * split between this cluster and the next one.
614 /* Advance the attr iterator to the start of the item
618 pango_attr_iterator_range (iter, &range_start, &range_end);
619 if (range_end > glyph_item->item->offset)
622 while (pango_attr_iterator_next (iter));
624 state.segment_attrs = pango_attr_iterator_get_attrs (iter);
626 /* Short circuit the case when we don't actually need to
629 if (range_start <= glyph_item->item->offset &&
630 range_end >= glyph_item->item->offset + glyph_item->item->length)
633 for (have_cluster = pango_glyph_item_iter_init_start (&state.iter, glyph_item, text);
635 have_cluster = pango_glyph_item_iter_next_cluster (&state.iter))
639 /* [range_start,range_end] is the first range that intersects
640 * the current cluster.
643 /* Split item into two, if this cluster isn't a continuation
644 * of the last cluster
646 if (start_new_segment)
648 result = g_slist_prepend (result,
649 split_before_cluster_start (&state));
650 state.segment_attrs = pango_attr_iterator_get_attrs (iter);
653 start_new_segment = FALSE;
655 /* Loop over all ranges that intersect this cluster; exiting
656 * leaving [range_start,range_end] being the first range that
657 * intersects the next cluster.
661 if (range_end > state.iter.end_index) /* Range intersects next cluster */
664 /* Since ranges end in this cluster, the next cluster goes into a
667 start_new_segment = TRUE;
669 have_next = pango_attr_iterator_next (iter);
670 pango_attr_iterator_range (iter, &range_start, &range_end);
672 if (range_start >= state.iter.end_index) /* New range doesn't intersect this cluster */
674 /* No gap between ranges, so previous range must of ended
675 * at cluster boundary.
677 g_assert (range_start == state.iter.end_index && start_new_segment);
681 /* If any ranges start *inside* this cluster, then we need
682 * to split the previous cluster into a separate segment
684 if (range_start > state.iter.start_index &&
685 state.iter.start_index != glyph_item->item->offset)
687 GSList *new_attrs = attr_slist_copy (state.segment_attrs);
688 result = g_slist_prepend (result,
689 split_before_cluster_start (&state));
690 state.segment_attrs = new_attrs;
693 state.segment_attrs = g_slist_concat (state.segment_attrs,
694 pango_attr_iterator_get_attrs (iter));
700 /* What's left in glyph_item is the remaining portion
702 append_attrs (glyph_item, state.segment_attrs);
703 result = g_slist_prepend (result, glyph_item);
705 if (LTR (glyph_item))
706 result = g_slist_reverse (result);
708 pango_attr_iterator_destroy (iter);
714 * pango_glyph_item_letter_space:
715 * @glyph_item: a #PangoGlyphItem
716 * @text: text that @glyph_item corresponds to
717 * (glyph_item->item->offset is an offset from the
719 * @log_attrs: logical attributes for the item (the
720 * first logical attribute refers to the position
721 * before the first character in the item)
722 * @letter_spacing: amount of letter spacing to add
723 * in Pango units. May be negative, though too large
724 * negative values will give ugly results.
726 * Adds spacing between the graphemes of @glyph_item to
727 * give the effect of typographic letter spacing.
732 pango_glyph_item_letter_space (PangoGlyphItem *glyph_item,
734 PangoLogAttr *log_attrs,
737 PangoGlyphItemIter iter;
738 PangoGlyphInfo *glyphs = glyph_item->glyphs->glyphs;
739 gboolean have_cluster;
740 int space_left, space_right;
742 space_left = letter_spacing / 2;
745 if ((letter_spacing & (PANGO_SCALE - 1)) == 0)
747 space_left = PANGO_UNITS_ROUND (space_left);
750 space_right = letter_spacing - space_left;
752 for (have_cluster = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
754 have_cluster = pango_glyph_item_iter_next_cluster (&iter))
756 if (!log_attrs[iter.start_char].is_cursor_position)
759 if (iter.start_glyph < iter.end_glyph) /* LTR */
761 if (iter.start_char > 0)
763 glyphs[iter.start_glyph].geometry.width += space_left ;
764 glyphs[iter.start_glyph].geometry.x_offset += space_left ;
766 if (iter.end_char < glyph_item->item->num_chars)
768 glyphs[iter.end_glyph-1].geometry.width += space_right;
773 if (iter.start_char > 0)
775 glyphs[iter.start_glyph].geometry.width += space_right;
777 if (iter.end_char < glyph_item->item->num_chars)
779 glyphs[iter.end_glyph+1].geometry.x_offset += space_left ;
780 glyphs[iter.end_glyph+1].geometry.width += space_left ;
787 * pango_glyph_item_get_logical_widths:
788 * @glyph_item: a #PangoGlyphItem
789 * @text: text that @glyph_item corresponds to
790 * (glyph_item->item->offset is an offset from the
792 * @logical_widths: an array whose length is the number of characters in
793 * glyph_item (equal to glyph_item->item->num_chars)
794 * to be filled in with the resulting character widths.
796 * Given a #PangoGlyphItem and the corresponding
797 * text, determine the screen width corresponding to each character. When
798 * multiple characters compose a single cluster, the width of the entire
799 * cluster is divided equally among the characters.
801 * See also pango_glyph_string_get_logical_widths().
806 pango_glyph_item_get_logical_widths (PangoGlyphItem *glyph_item,
810 PangoGlyphItemIter iter;
811 gboolean has_cluster;
814 dir = glyph_item->item->analysis.level % 2 == 0 ? +1 : -1;
815 for (has_cluster = pango_glyph_item_iter_init_start (&iter, glyph_item, text);
817 has_cluster = pango_glyph_item_iter_next_cluster (&iter))
819 int glyph_index, char_index, num_chars, cluster_width = 0, char_width;
821 for (glyph_index = iter.start_glyph;
822 glyph_index != iter.end_glyph;
825 cluster_width += glyph_item->glyphs->glyphs[glyph_index].geometry.width;
828 num_chars = iter.end_char - iter.start_char;
829 if (num_chars) /* pedantic */
831 char_width = cluster_width / num_chars;
833 for (char_index = iter.start_char;
834 char_index < iter.end_char;
837 logical_widths[char_index] = char_width;
840 /* add any residues to the first char */
841 logical_widths[iter.start_char] += cluster_width - (char_width * num_chars);