1/* Pango
2 * pango-glyph-item.c: Pair of PangoItem and a glyph string
3 *
4 * Copyright (C) 2002 Red Hat Software
5 *
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.
10 *
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.
15 *
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.
20 */
21
22#include "config.h"
23#include <string.h>
24
25#include "pango-glyph-item.h"
26#include "pango-impl-utils.h"
27#include "pango-attributes-private.h"
28
29#define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0)
30
31/**
32 * pango_glyph_item_split:
33 * @orig: a `PangoItem`
34 * @text: text to which positions in @orig apply
35 * @split_index: byte index of position to split item, relative to the
36 * start of the item
37 *
38 * Modifies @orig to cover only the text after @split_index, and
39 * returns a new item that covers the text before @split_index that
40 * used to be in @orig.
41 *
42 * You can think of @split_index as the length of the returned item.
43 * @split_index may not be 0, and it may not be greater than or equal
44 * to the length of @orig (that is, there must be at least one byte
45 * assigned to each item, you can't create a zero-length item).
46 *
47 * This function is similar in function to pango_item_split() (and uses
48 * it internally.)
49 *
50 * Return value: the newly allocated item representing text before
51 * @split_index, which should be freed
52 * with pango_glyph_item_free().
53 *
54 * Since: 1.2
55 */
56PangoGlyphItem *
57pango_glyph_item_split (PangoGlyphItem *orig,
58 const char *text,
59 int split_index)
60{
61 PangoGlyphItem *new;
62 int i;
63 int num_glyphs;
64 int num_remaining;
65 int split_offset;
66
67 g_return_val_if_fail (orig != NULL, NULL);
68 g_return_val_if_fail (orig->item->length > 0, NULL);
69 g_return_val_if_fail (split_index > 0, NULL);
70 g_return_val_if_fail (split_index < orig->item->length, NULL);
71
72 if (LTR (orig))
73 {
74 for (i = 0; i < orig->glyphs->num_glyphs; i++)
75 {
76 if (orig->glyphs->log_clusters[i] >= split_index)
77 break;
78 }
79
80 if (i == orig->glyphs->num_glyphs) /* No splitting necessary */
81 return NULL;
82
83 split_index = orig->glyphs->log_clusters[i];
84 num_glyphs = i;
85 }
86 else
87 {
88 for (i = orig->glyphs->num_glyphs - 1; i >= 0; i--)
89 {
90 if (orig->glyphs->log_clusters[i] >= split_index)
91 break;
92 }
93
94 if (i < 0) /* No splitting necessary */
95 return NULL;
96
97 split_index = orig->glyphs->log_clusters[i];
98 num_glyphs = orig->glyphs->num_glyphs - 1 - i;
99 }
100
101 num_remaining = orig->glyphs->num_glyphs - num_glyphs;
102
103 new = g_slice_new (PangoGlyphItem);
104 split_offset = g_utf8_pointer_to_offset (str: text + orig->item->offset,
105 pos: text + orig->item->offset + split_index);
106 new->item = pango_item_split (orig: orig->item, split_index, split_offset);
107
108 new->glyphs = pango_glyph_string_new ();
109 pango_glyph_string_set_size (string: new->glyphs, new_len: num_glyphs);
110
111 if (LTR (orig))
112 {
113 memcpy (dest: new->glyphs->glyphs, src: orig->glyphs->glyphs, n: num_glyphs * sizeof (PangoGlyphInfo));
114 memcpy (dest: new->glyphs->log_clusters, src: orig->glyphs->log_clusters, n: num_glyphs * sizeof (int));
115
116 memmove (dest: orig->glyphs->glyphs, src: orig->glyphs->glyphs + num_glyphs,
117 n: num_remaining * sizeof (PangoGlyphInfo));
118 for (i = num_glyphs; i < orig->glyphs->num_glyphs; i++)
119 orig->glyphs->log_clusters[i - num_glyphs] = orig->glyphs->log_clusters[i] - split_index;
120 }
121 else
122 {
123 memcpy (dest: new->glyphs->glyphs, src: orig->glyphs->glyphs + num_remaining, n: num_glyphs * sizeof (PangoGlyphInfo));
124 memcpy (dest: new->glyphs->log_clusters, src: orig->glyphs->log_clusters + num_remaining, n: num_glyphs * sizeof (int));
125
126 for (i = 0; i < num_remaining; i++)
127 orig->glyphs->log_clusters[i] = orig->glyphs->log_clusters[i] - split_index;
128 }
129
130 pango_glyph_string_set_size (string: orig->glyphs, new_len: orig->glyphs->num_glyphs - num_glyphs);
131
132 new->y_offset = orig->y_offset;
133 new->start_x_offset = orig->start_x_offset;
134 new->end_x_offset = -orig->start_x_offset;
135
136 return new;
137}
138
139/**
140 * pango_glyph_item_copy:
141 * @orig: (nullable): a `PangoGlyphItem`
142 *
143 * Make a deep copy of an existing `PangoGlyphItem` structure.
144 *
145 * Return value: (nullable): the newly allocated `PangoGlyphItem`
146 *
147 * Since: 1.20
148 */
149PangoGlyphItem *
150pango_glyph_item_copy (PangoGlyphItem *orig)
151{
152 PangoGlyphItem *result;
153
154 if (orig == NULL)
155 return NULL;
156
157 result = g_slice_new (PangoGlyphItem);
158
159 result->item = pango_item_copy (item: orig->item);
160 result->glyphs = pango_glyph_string_copy (string: orig->glyphs);
161 result->y_offset = orig->y_offset;
162 result->start_x_offset = orig->start_x_offset;
163 result->end_x_offset = orig->end_x_offset;
164
165 return result;
166}
167
168/**
169 * pango_glyph_item_free:
170 * @glyph_item: (nullable): a `PangoGlyphItem`
171 *
172 * Frees a `PangoGlyphItem` and resources to which it points.
173 *
174 * Since: 1.6
175 */
176void
177pango_glyph_item_free (PangoGlyphItem *glyph_item)
178{
179 if (glyph_item == NULL)
180 return;
181
182 if (glyph_item->item)
183 pango_item_free (item: glyph_item->item);
184 if (glyph_item->glyphs)
185 pango_glyph_string_free (string: glyph_item->glyphs);
186
187 g_slice_free (PangoGlyphItem, glyph_item);
188}
189
190G_DEFINE_BOXED_TYPE (PangoGlyphItem, pango_glyph_item,
191 pango_glyph_item_copy,
192 pango_glyph_item_free);
193
194
195/**
196 * pango_glyph_item_iter_copy:
197 * @orig: (nullable): a `PangoGlyphItem`Iter
198 *
199 * Make a shallow copy of an existing `PangoGlyphItemIter` structure.
200 *
201 * Return value: (nullable): the newly allocated `PangoGlyphItemIter`
202 *
203 * Since: 1.22
204 */
205PangoGlyphItemIter *
206pango_glyph_item_iter_copy (PangoGlyphItemIter *orig)
207{
208 PangoGlyphItemIter *result;
209
210 if (orig == NULL)
211 return NULL;
212
213 result = g_slice_new (PangoGlyphItemIter);
214
215 *result = *orig;
216
217 return result;
218}
219
220/**
221 * pango_glyph_item_iter_free:
222 * @iter: (nullable): a `PangoGlyphItemIter`
223 *
224 * Frees a `PangoGlyphItem`Iter.
225 *
226 * Since: 1.22
227 */
228void
229pango_glyph_item_iter_free (PangoGlyphItemIter *iter)
230{
231 if (iter == NULL)
232 return;
233
234 g_slice_free (PangoGlyphItemIter, iter);
235}
236
237G_DEFINE_BOXED_TYPE (PangoGlyphItemIter, pango_glyph_item_iter,
238 pango_glyph_item_iter_copy,
239 pango_glyph_item_iter_free)
240
241/**
242 * pango_glyph_item_iter_next_cluster:
243 * @iter: a `PangoGlyphItemIter`
244 *
245 * Advances the iterator to the next cluster in the glyph item.
246 *
247 * See `PangoGlyphItemIter` for details of cluster orders.
248 *
249 * Return value: %TRUE if the iterator was advanced,
250 * %FALSE if we were already on the last cluster.
251 *
252 * Since: 1.22
253 */
254gboolean
255pango_glyph_item_iter_next_cluster (PangoGlyphItemIter *iter)
256{
257 int glyph_index = iter->end_glyph;
258 PangoGlyphString *glyphs = iter->glyph_item->glyphs;
259 int cluster;
260 PangoItem *item = iter->glyph_item->item;
261
262 if (LTR (iter->glyph_item))
263 {
264 if (glyph_index == glyphs->num_glyphs)
265 return FALSE;
266 }
267 else
268 {
269 if (glyph_index < 0)
270 return FALSE;
271 }
272
273 iter->start_glyph = iter->end_glyph;
274 iter->start_index = iter->end_index;
275 iter->start_char = iter->end_char;
276
277 if (LTR (iter->glyph_item))
278 {
279 cluster = glyphs->log_clusters[glyph_index];
280 while (TRUE)
281 {
282 glyph_index++;
283
284 if (glyph_index == glyphs->num_glyphs)
285 {
286 iter->end_index = item->offset + item->length;
287 iter->end_char = item->num_chars;
288 break;
289 }
290
291 if (glyphs->log_clusters[glyph_index] > cluster)
292 {
293 iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
294 iter->end_char += pango_utf8_strlen (p: iter->text + iter->start_index,
295 max: iter->end_index - iter->start_index);
296 break;
297 }
298 }
299 }
300 else /* RTL */
301 {
302 cluster = glyphs->log_clusters[glyph_index];
303 while (TRUE)
304 {
305 glyph_index--;
306
307 if (glyph_index < 0)
308 {
309 iter->end_index = item->offset + item->length;
310 iter->end_char = item->num_chars;
311 break;
312 }
313
314 if (glyphs->log_clusters[glyph_index] > cluster)
315 {
316 iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
317 iter->end_char += pango_utf8_strlen (p: iter->text + iter->start_index,
318 max: iter->end_index - iter->start_index);
319 break;
320 }
321 }
322 }
323
324 iter->end_glyph = glyph_index;
325
326 g_assert (iter->start_char <= iter->end_char);
327 g_assert (iter->end_char <= item->num_chars);
328
329 return TRUE;
330}
331
332/**
333 * pango_glyph_item_iter_prev_cluster:
334 * @iter: a `PangoGlyphItemIter`
335 *
336 * Moves the iterator to the preceding cluster in the glyph item.
337 * See `PangoGlyphItemIter` for details of cluster orders.
338 *
339 * Return value: %TRUE if the iterator was moved,
340 * %FALSE if we were already on the first cluster.
341 *
342 * Since: 1.22
343 */
344gboolean
345pango_glyph_item_iter_prev_cluster (PangoGlyphItemIter *iter)
346{
347 int glyph_index = iter->start_glyph;
348 PangoGlyphString *glyphs = iter->glyph_item->glyphs;
349 int cluster;
350 PangoItem *item = iter->glyph_item->item;
351
352 if (LTR (iter->glyph_item))
353 {
354 if (glyph_index == 0)
355 return FALSE;
356 }
357 else
358 {
359 if (glyph_index == glyphs->num_glyphs - 1)
360 return FALSE;
361
362 }
363
364 iter->end_glyph = iter->start_glyph;
365 iter->end_index = iter->start_index;
366 iter->end_char = iter->start_char;
367
368 if (LTR (iter->glyph_item))
369 {
370 cluster = glyphs->log_clusters[glyph_index - 1];
371 while (TRUE)
372 {
373 if (glyph_index == 0)
374 {
375 iter->start_index = item->offset;
376 iter->start_char = 0;
377 break;
378 }
379
380 glyph_index--;
381
382 if (glyphs->log_clusters[glyph_index] < cluster)
383 {
384 glyph_index++;
385 iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
386 iter->start_char -= pango_utf8_strlen (p: iter->text + iter->start_index,
387 max: iter->end_index - iter->start_index);
388 break;
389 }
390 }
391 }
392 else /* RTL */
393 {
394 cluster = glyphs->log_clusters[glyph_index + 1];
395 while (TRUE)
396 {
397 if (glyph_index == glyphs->num_glyphs - 1)
398 {
399 iter->start_index = item->offset;
400 iter->start_char = 0;
401 break;
402 }
403
404 glyph_index++;
405
406 if (glyphs->log_clusters[glyph_index] < cluster)
407 {
408 glyph_index--;
409 iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
410 iter->start_char -= pango_utf8_strlen (p: iter->text + iter->start_index,
411 max: iter->end_index - iter->start_index);
412 break;
413 }
414 }
415 }
416
417 iter->start_glyph = glyph_index;
418
419 g_assert (iter->start_char <= iter->end_char);
420 g_assert (0 <= iter->start_char);
421
422 return TRUE;
423}
424
425/**
426 * pango_glyph_item_iter_init_start:
427 * @iter: a `PangoGlyphItemIter`
428 * @glyph_item: the glyph item to iterate over
429 * @text: text corresponding to the glyph item
430 *
431 * Initializes a `PangoGlyphItemIter` structure to point to the
432 * first cluster in a glyph item.
433 *
434 * See `PangoGlyphItemIter` for details of cluster orders.
435 *
436 * Return value: %FALSE if there are no clusters in the glyph item
437 *
438 * Since: 1.22
439 */
440gboolean
441pango_glyph_item_iter_init_start (PangoGlyphItemIter *iter,
442 PangoGlyphItem *glyph_item,
443 const char *text)
444{
445 iter->glyph_item = glyph_item;
446 iter->text = text;
447
448 if (LTR (glyph_item))
449 iter->end_glyph = 0;
450 else
451 iter->end_glyph = glyph_item->glyphs->num_glyphs - 1;
452
453 iter->end_index = glyph_item->item->offset;
454 iter->end_char = 0;
455
456 iter->start_glyph = iter->end_glyph;
457 iter->start_index = iter->end_index;
458 iter->start_char = iter->end_char;
459
460 /* Advance onto the first cluster of the glyph item */
461 return pango_glyph_item_iter_next_cluster (iter);
462}
463
464/**
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
469 *
470 * Initializes a `PangoGlyphItemIter` structure to point to the
471 * last cluster in a glyph item.
472 *
473 * See `PangoGlyphItemIter` for details of cluster orders.
474 *
475 * Return value: %FALSE if there are no clusters in the glyph item
476 *
477 * Since: 1.22
478 */
479gboolean
480pango_glyph_item_iter_init_end (PangoGlyphItemIter *iter,
481 PangoGlyphItem *glyph_item,
482 const char *text)
483{
484 iter->glyph_item = glyph_item;
485 iter->text = text;
486
487 if (LTR (glyph_item))
488 iter->start_glyph = glyph_item->glyphs->num_glyphs;
489 else
490 iter->start_glyph = -1;
491
492 iter->start_index = glyph_item->item->offset + glyph_item->item->length;
493 iter->start_char = glyph_item->item->num_chars;
494
495 iter->end_glyph = iter->start_glyph;
496 iter->end_index = iter->start_index;
497 iter->end_char = iter->start_char;
498
499 /* Advance onto the first cluster of the glyph item */
500 return pango_glyph_item_iter_prev_cluster (iter);
501}
502
503typedef struct
504{
505 PangoGlyphItemIter iter;
506
507 GSList *segment_attrs;
508} ApplyAttrsState;
509
510/* Tack @attrs onto the attributes of glyph_item
511 */
512static void
513append_attrs (PangoGlyphItem *glyph_item,
514 GSList *attrs)
515{
516 glyph_item->item->analysis.extra_attrs =
517 g_slist_concat (list1: glyph_item->item->analysis.extra_attrs, list2: attrs);
518}
519
520/* Make a deep copy of a GSList of PangoAttribute
521 */
522static GSList *
523attr_slist_copy (GSList *attrs)
524{
525 GSList *tmp_list;
526 GSList *new_attrs;
527
528 new_attrs = g_slist_copy (list: attrs);
529
530 for (tmp_list = new_attrs; tmp_list; tmp_list = tmp_list->next)
531 tmp_list->data = pango_attribute_copy (attr: tmp_list->data);
532
533 return new_attrs;
534}
535
536/* Split the glyph item at the start of the current cluster
537 */
538static PangoGlyphItem *
539split_before_cluster_start (ApplyAttrsState *state)
540{
541 PangoGlyphItem *split_item;
542 int split_len = state->iter.start_index - state->iter.glyph_item->item->offset;
543
544 split_item = pango_glyph_item_split (orig: state->iter.glyph_item, text: state->iter.text, split_index: split_len);
545 append_attrs (glyph_item: split_item, attrs: state->segment_attrs);
546
547 /* Adjust iteration to account for the split
548 */
549 if (LTR (state->iter.glyph_item))
550 {
551 state->iter.start_glyph -= split_item->glyphs->num_glyphs;
552 state->iter.end_glyph -= split_item->glyphs->num_glyphs;
553 }
554
555 state->iter.start_char -= split_item->item->num_chars;
556 state->iter.end_char -= split_item->item->num_chars;
557
558 return split_item;
559}
560
561/**
562 * pango_glyph_item_apply_attrs:
563 * @glyph_item: a shaped item
564 * @text: text that @list applies to
565 * @list: a `PangoAttrList`
566 *
567 * Splits a shaped item (`PangoGlyphItem`) into multiple items based
568 * on an attribute list.
569 *
570 * The idea is that if you have attributes that don't affect shaping,
571 * such as color or underline, to avoid affecting shaping, you filter
572 * them out ([method@Pango.AttrList.filter]), apply the shaping process
573 * and then reapply them to the result using this function.
574 *
575 * All attributes that start or end inside a cluster are applied
576 * to that cluster; for instance, if half of a cluster is underlined
577 * and the other-half strikethrough, then the cluster will end
578 * up with both underline and strikethrough attributes. In these
579 * cases, it may happen that @item->extra_attrs for some of the
580 * result items can have multiple attributes of the same type.
581 *
582 * This function takes ownership of @glyph_item; it will be reused
583 * as one of the elements in the list.
584 *
585 * Return value: (transfer full) (element-type Pango.GlyphItem): a
586 * list of glyph items resulting from splitting @glyph_item. Free
587 * the elements using [method@Pango.GlyphItem.free], the list using
588 * g_slist_free().
589 *
590 * Since: 1.2
591 */
592GSList *
593pango_glyph_item_apply_attrs (PangoGlyphItem *glyph_item,
594 const char *text,
595 PangoAttrList *list)
596{
597 PangoAttrIterator iter;
598 GSList *result = NULL;
599 ApplyAttrsState state;
600 gboolean start_new_segment = FALSE;
601 gboolean have_cluster;
602 int range_start, range_end;
603 gboolean is_ellipsis;
604
605 /* This routine works by iterating through the item cluster by
606 * cluster; we accumulate the attributes that we need to
607 * add to the next output item, and decide when to split
608 * off an output item based on two criteria:
609 *
610 * A) If start_index < attribute_start < end_index
611 * (attribute starts within cluster) then we need
612 * to split between the last cluster and this cluster.
613 * B) If start_index < attribute_end <= end_index,
614 * (attribute ends within cluster) then we need to
615 * split between this cluster and the next one.
616 */
617
618 /* Advance the attr iterator to the start of the item
619 */
620 _pango_attr_list_get_iterator (list, iterator: &iter);
621 do
622 {
623 pango_attr_iterator_range (iterator: &iter, start: &range_start, end: &range_end);
624 if (range_end > glyph_item->item->offset)
625 break;
626 }
627 while (pango_attr_iterator_next (iterator: &iter));
628
629 state.segment_attrs = pango_attr_iterator_get_attrs (iterator: &iter);
630
631 is_ellipsis = (glyph_item->item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS) != 0;
632
633 /* Short circuit the case when we don't actually need to
634 * split the item
635 */
636 if (is_ellipsis ||
637 (range_start <= glyph_item->item->offset &&
638 range_end >= glyph_item->item->offset + glyph_item->item->length))
639 goto out;
640
641 for (have_cluster = pango_glyph_item_iter_init_start (iter: &state.iter, glyph_item, text);
642 have_cluster;
643 have_cluster = pango_glyph_item_iter_next_cluster (iter: &state.iter))
644 {
645 gboolean have_next;
646
647 /* [range_start,range_end] is the first range that intersects
648 * the current cluster.
649 */
650
651 /* Split item into two, if this cluster isn't a continuation
652 * of the last cluster
653 */
654 if (start_new_segment)
655 {
656 result = g_slist_prepend (list: result,
657 data: split_before_cluster_start (state: &state));
658 state.segment_attrs = pango_attr_iterator_get_attrs (iterator: &iter);
659 }
660
661 start_new_segment = FALSE;
662
663 /* Loop over all ranges that intersect this cluster; exiting
664 * leaving [range_start,range_end] being the first range that
665 * intersects the next cluster.
666 */
667 do
668 {
669 if (range_end > state.iter.end_index) /* Range intersects next cluster */
670 break;
671
672 /* Since ranges end in this cluster, the next cluster goes into a
673 * separate segment
674 */
675 start_new_segment = TRUE;
676
677 have_next = pango_attr_iterator_next (iterator: &iter);
678 pango_attr_iterator_range (iterator: &iter, start: &range_start, end: &range_end);
679
680 if (range_start >= state.iter.end_index) /* New range doesn't intersect this cluster */
681 {
682 /* No gap between ranges, so previous range must of ended
683 * at cluster boundary.
684 */
685 g_assert (range_start == state.iter.end_index && start_new_segment);
686 break;
687 }
688
689 /* If any ranges start *inside* this cluster, then we need
690 * to split the previous cluster into a separate segment
691 */
692 if (range_start > state.iter.start_index &&
693 state.iter.start_index != glyph_item->item->offset)
694 {
695 GSList *new_attrs = attr_slist_copy (attrs: state.segment_attrs);
696 result = g_slist_prepend (list: result,
697 data: split_before_cluster_start (state: &state));
698 state.segment_attrs = new_attrs;
699 }
700
701 state.segment_attrs = g_slist_concat (list1: state.segment_attrs,
702 list2: pango_attr_iterator_get_attrs (iterator: &iter));
703 }
704 while (have_next);
705 }
706
707 out:
708 /* What's left in glyph_item is the remaining portion
709 */
710 append_attrs (glyph_item, attrs: state.segment_attrs);
711 result = g_slist_prepend (list: result, data: glyph_item);
712
713 if (LTR (glyph_item))
714 result = g_slist_reverse (list: result);
715
716 _pango_attr_iterator_destroy (iterator: &iter);
717
718 return result;
719}
720
721/**
722 * pango_glyph_item_letter_space:
723 * @glyph_item: a `PangoGlyphItem`
724 * @text: text that @glyph_item corresponds to
725 * (glyph_item->item->offset is an offset from the
726 * start of @text)
727 * @log_attrs: (array): logical attributes for the item
728 * (the first logical attribute refers to the position
729 * before the first character in the item)
730 * @letter_spacing: amount of letter spacing to add
731 * in Pango units. May be negative, though too large
732 * negative values will give ugly results.
733 *
734 * Adds spacing between the graphemes of @glyph_item to
735 * give the effect of typographic letter spacing.
736 *
737 * Since: 1.6
738 */
739void
740pango_glyph_item_letter_space (PangoGlyphItem *glyph_item,
741 const char *text,
742 PangoLogAttr *log_attrs,
743 int letter_spacing)
744{
745 PangoGlyphItemIter iter;
746 PangoGlyphInfo *glyphs = glyph_item->glyphs->glyphs;
747 gboolean have_cluster;
748 int space_left, space_right;
749
750 space_left = letter_spacing / 2;
751
752 /* hinting */
753 if ((letter_spacing & (PANGO_SCALE - 1)) == 0)
754 {
755 space_left = PANGO_UNITS_ROUND (space_left);
756 }
757
758 space_right = letter_spacing - space_left;
759
760 for (have_cluster = pango_glyph_item_iter_init_start (iter: &iter, glyph_item, text);
761 have_cluster;
762 have_cluster = pango_glyph_item_iter_next_cluster (iter: &iter))
763 {
764 if (!log_attrs[iter.start_char].is_cursor_position)
765 {
766 if (glyphs[iter.start_glyph].geometry.width == 0)
767 {
768 if (iter.start_glyph < iter.end_glyph) /* LTR */
769 glyphs[iter.start_glyph].geometry.x_offset -= space_right;
770 else
771 glyphs[iter.start_glyph].geometry.x_offset += space_left;
772 }
773 continue;
774 }
775
776 if (iter.start_glyph < iter.end_glyph) /* LTR */
777 {
778 if (iter.start_char > 0)
779 {
780 glyphs[iter.start_glyph].geometry.width += space_left ;
781 glyphs[iter.start_glyph].geometry.x_offset += space_left ;
782 }
783 if (iter.end_char < glyph_item->item->num_chars)
784 {
785 glyphs[iter.end_glyph-1].geometry.width += space_right;
786 }
787 }
788 else /* RTL */
789 {
790 if (iter.start_char > 0)
791 {
792 glyphs[iter.start_glyph].geometry.width += space_right;
793 }
794 if (iter.end_char < glyph_item->item->num_chars)
795 {
796 glyphs[iter.end_glyph+1].geometry.x_offset += space_left ;
797 glyphs[iter.end_glyph+1].geometry.width += space_left ;
798 }
799 }
800 }
801}
802
803/**
804 * pango_glyph_item_get_logical_widths:
805 * @glyph_item: a `PangoGlyphItem`
806 * @text: text that @glyph_item corresponds to
807 * (glyph_item->item->offset is an offset from the
808 * start of @text)
809 * @logical_widths: (array): an array whose length is the number of
810 * characters in glyph_item (equal to glyph_item->item->num_chars)
811 * to be filled in with the resulting character widths.
812 *
813 * Given a `PangoGlyphItem` and the corresponding text, determine the
814 * width corresponding to each character.
815 *
816 * When multiple characters compose a single cluster, the width of the
817 * entire cluster is divided equally among the characters.
818 *
819 * See also [method@Pango.GlyphString.get_logical_widths].
820 *
821 * Since: 1.26
822 */
823void
824pango_glyph_item_get_logical_widths (PangoGlyphItem *glyph_item,
825 const char *text,
826 int *logical_widths)
827{
828 PangoGlyphItemIter iter;
829 gboolean has_cluster;
830 int dir;
831
832 dir = glyph_item->item->analysis.level % 2 == 0 ? +1 : -1;
833 for (has_cluster = pango_glyph_item_iter_init_start (iter: &iter, glyph_item, text);
834 has_cluster;
835 has_cluster = pango_glyph_item_iter_next_cluster (iter: &iter))
836 {
837 int glyph_index, char_index, num_chars, cluster_width = 0, char_width;
838
839 for (glyph_index = iter.start_glyph;
840 glyph_index != iter.end_glyph;
841 glyph_index += dir)
842 {
843 cluster_width += glyph_item->glyphs->glyphs[glyph_index].geometry.width;
844 }
845
846 num_chars = iter.end_char - iter.start_char;
847 if (num_chars) /* pedantic */
848 {
849 char_width = cluster_width / num_chars;
850
851 for (char_index = iter.start_char;
852 char_index < iter.end_char;
853 char_index++)
854 {
855 logical_widths[char_index] = char_width;
856 }
857
858 /* add any residues to the first char */
859 logical_widths[iter.start_char] += cluster_width - (char_width * num_chars);
860 }
861 }
862}
863

source code of gtk/subprojects/pango/pango/pango-glyph-item.c