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 | */ |
56 | PangoGlyphItem * |
57 | pango_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 | */ |
149 | PangoGlyphItem * |
150 | pango_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 | */ |
176 | void |
177 | pango_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 | |
190 | G_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 | */ |
205 | PangoGlyphItemIter * |
206 | pango_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 | */ |
228 | void |
229 | pango_glyph_item_iter_free (PangoGlyphItemIter *iter) |
230 | { |
231 | if (iter == NULL) |
232 | return; |
233 | |
234 | g_slice_free (PangoGlyphItemIter, iter); |
235 | } |
236 | |
237 | G_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 | */ |
254 | gboolean |
255 | pango_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 | */ |
344 | gboolean |
345 | pango_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 | */ |
440 | gboolean |
441 | pango_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 | */ |
479 | gboolean |
480 | pango_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 | |
503 | typedef struct |
504 | { |
505 | PangoGlyphItemIter ; |
506 | |
507 | GSList *; |
508 | } ; |
509 | |
510 | /* Tack @attrs onto the attributes of glyph_item |
511 | */ |
512 | static void |
513 | append_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 | */ |
522 | static GSList * |
523 | attr_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 | */ |
538 | static PangoGlyphItem * |
539 | split_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 | */ |
592 | GSList * |
593 | pango_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 | */ |
739 | void |
740 | pango_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 | */ |
823 | void |
824 | pango_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 | |