1/* Pango
2 * itemize.c: Turning text into items
3 *
4 * Copyright (C) 2000, 2006 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#include <stdlib.h>
25
26#include "pango-context-private.h"
27#include "pango-impl-utils.h"
28
29#include "pango-font-private.h"
30#include "pango-fontset.h"
31#include "pango-fontmap-private.h"
32#include "pango-script-private.h"
33#include "pango-emoji-private.h"
34#include "pango-attributes-private.h"
35#include "pango-item-private.h"
36
37#include <hb-ot.h>
38
39/* {{{ Font cache */
40
41/*
42 * We cache the results of character,fontset => font in a hash table
43 */
44
45typedef struct {
46 GHashTable *hash;
47} FontCache;
48
49typedef struct {
50 PangoFont *font;
51 int position; /* position of the font in the fontset */
52} FontElement;
53
54static void
55font_cache_destroy (FontCache *cache)
56{
57 g_hash_table_destroy (hash_table: cache->hash);
58 g_slice_free (FontCache, cache);
59}
60
61static void
62font_element_destroy (FontElement *element)
63{
64 if (element->font)
65 g_object_unref (object: element->font);
66 g_slice_free (FontElement, element);
67}
68
69static FontCache *
70get_font_cache (PangoFontset *fontset)
71{
72 FontCache *cache;
73
74 static GQuark cache_quark = 0; /* MT-safe */
75 if (G_UNLIKELY (!cache_quark))
76 cache_quark = g_quark_from_static_string (string: "pango-font-cache");
77
78retry:
79 cache = g_object_get_qdata (G_OBJECT (fontset), quark: cache_quark);
80 if (G_UNLIKELY (!cache))
81 {
82 cache = g_slice_new (FontCache);
83 cache->hash = g_hash_table_new_full (hash_func: g_direct_hash, NULL,
84 NULL, value_destroy_func: (GDestroyNotify)font_element_destroy);
85 if (!g_object_replace_qdata (G_OBJECT (fontset), quark: cache_quark, NULL,
86 newval: cache, destroy: (GDestroyNotify)font_cache_destroy,
87 NULL))
88 {
89 font_cache_destroy (cache);
90 goto retry;
91 }
92 }
93
94 return cache;
95}
96
97static gboolean
98font_cache_get (FontCache *cache,
99 gunichar wc,
100 PangoFont **font,
101 int *position)
102{
103 FontElement *element;
104
105 element = g_hash_table_lookup (hash_table: cache->hash, GUINT_TO_POINTER (wc));
106 if (element)
107 {
108 *font = element->font;
109 *position = element->position;
110 return TRUE;
111 }
112 else
113 return FALSE;
114}
115
116static void
117font_cache_insert (FontCache *cache,
118 gunichar wc,
119 PangoFont *font,
120 int position)
121{
122 FontElement *element = g_slice_new (FontElement);
123 element->font = font ? g_object_ref (font) : NULL;
124 element->position = position;
125
126 g_hash_table_insert (hash_table: cache->hash, GUINT_TO_POINTER (wc), value: element);
127}
128
129/* }}} */
130/* {{{ Width Iter */
131
132typedef struct _PangoWidthIter PangoWidthIter;
133
134struct _PangoWidthIter
135{
136 const gchar *text_start;
137 const gchar *text_end;
138 const gchar *start;
139 const gchar *end;
140 gboolean upright;
141};
142
143static gboolean
144width_iter_is_upright (gunichar ch)
145{
146 /* https://www.unicode.org/Public/11.0.0/ucd/VerticalOrientation.txt
147 * VO=U or Tu table generated by tools/gen-vertical-orientation-U-table.py.
148 *
149 * FIXME: In the future, If GLib supports VerticalOrientation, please use it.
150 */
151 static const gunichar upright[][2] = {
152 {0x00A7, 0x00A7}, {0x00A9, 0x00A9}, {0x00AE, 0x00AE}, {0x00B1, 0x00B1},
153 {0x00BC, 0x00BE}, {0x00D7, 0x00D7}, {0x00F7, 0x00F7}, {0x02EA, 0x02EB},
154 {0x1100, 0x11FF}, {0x1401, 0x167F}, {0x18B0, 0x18FF}, {0x2016, 0x2016},
155 {0x2020, 0x2021}, {0x2030, 0x2031}, {0x203B, 0x203C}, {0x2042, 0x2042},
156 {0x2047, 0x2049}, {0x2051, 0x2051}, {0x2065, 0x2065}, {0x20DD, 0x20E0},
157 {0x20E2, 0x20E4}, {0x2100, 0x2101}, {0x2103, 0x2109}, {0x210F, 0x210F},
158 {0x2113, 0x2114}, {0x2116, 0x2117}, {0x211E, 0x2123}, {0x2125, 0x2125},
159 {0x2127, 0x2127}, {0x2129, 0x2129}, {0x212E, 0x212E}, {0x2135, 0x213F},
160 {0x2145, 0x214A}, {0x214C, 0x214D}, {0x214F, 0x2189}, {0x218C, 0x218F},
161 {0x221E, 0x221E}, {0x2234, 0x2235}, {0x2300, 0x2307}, {0x230C, 0x231F},
162 {0x2324, 0x2328}, {0x232B, 0x232B}, {0x237D, 0x239A}, {0x23BE, 0x23CD},
163 {0x23CF, 0x23CF}, {0x23D1, 0x23DB}, {0x23E2, 0x2422}, {0x2424, 0x24FF},
164 {0x25A0, 0x2619}, {0x2620, 0x2767}, {0x2776, 0x2793}, {0x2B12, 0x2B2F},
165 {0x2B50, 0x2B59}, {0x2BB8, 0x2BD1}, {0x2BD3, 0x2BEB}, {0x2BF0, 0x2BFF},
166 {0x2E80, 0x3007}, {0x3012, 0x3013}, {0x3020, 0x302F}, {0x3031, 0x309F},
167 {0x30A1, 0x30FB}, {0x30FD, 0xA4CF}, {0xA960, 0xA97F}, {0xAC00, 0xD7FF},
168 {0xE000, 0xFAFF}, {0xFE10, 0xFE1F}, {0xFE30, 0xFE48}, {0xFE50, 0xFE57},
169 {0xFE5F, 0xFE62}, {0xFE67, 0xFE6F}, {0xFF01, 0xFF07}, {0xFF0A, 0xFF0C},
170 {0xFF0E, 0xFF19}, {0xFF1F, 0xFF3A}, {0xFF3C, 0xFF3C}, {0xFF3E, 0xFF3E},
171 {0xFF40, 0xFF5A}, {0xFFE0, 0xFFE2}, {0xFFE4, 0xFFE7}, {0xFFF0, 0xFFF8},
172 {0xFFFC, 0xFFFD}, {0x10980, 0x1099F}, {0x11580, 0x115FF}, {0x11A00, 0x11AAF},
173 {0x13000, 0x1342F}, {0x14400, 0x1467F}, {0x16FE0, 0x18AFF}, {0x1B000, 0x1B12F},
174 {0x1B170, 0x1B2FF}, {0x1D000, 0x1D1FF}, {0x1D2E0, 0x1D37F}, {0x1D800, 0x1DAAF},
175 {0x1F000, 0x1F7FF}, {0x1F900, 0x1FA6F}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
176 {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}
177 };
178 static const int max = sizeof(upright) / sizeof(upright[0]);
179 int st = 0;
180 int ed = max;
181
182 if (ch < upright[0][0])
183 return FALSE;
184
185 while (st <= ed)
186 {
187 int mid = (st + ed) / 2;
188 if (upright[mid][0] <= ch && ch <= upright[mid][1])
189 return TRUE;
190 else
191 if (upright[mid][0] <= ch)
192 st = mid + 1;
193 else
194 ed = mid - 1;
195 }
196
197 return FALSE;
198}
199
200static void
201width_iter_next (PangoWidthIter *iter)
202{
203 gboolean met_joiner = FALSE;
204 iter->start = iter->end;
205
206 if (iter->end < iter->text_end)
207 {
208 gunichar ch = g_utf8_get_char (p: iter->end);
209 iter->upright = width_iter_is_upright (ch);
210 }
211
212 while (iter->end < iter->text_end)
213 {
214 gunichar ch = g_utf8_get_char (p: iter->end);
215
216 /* for zero width joiner */
217 if (ch == 0x200D)
218 {
219 iter->end = g_utf8_next_char (iter->end);
220 met_joiner = TRUE;
221 continue;
222 }
223
224 /* ignore the upright check if met joiner */
225 if (met_joiner)
226 {
227 iter->end = g_utf8_next_char (iter->end);
228 met_joiner = FALSE;
229 continue;
230 }
231
232 /* for variation selector, tag and emoji modifier. */
233 if (G_UNLIKELY (ch == 0xFE0EU || ch == 0xFE0FU ||
234 (ch >= 0xE0020 && ch <= 0xE007F) ||
235 (ch >= 0x1F3FB && ch <= 0x1F3FF)))
236 {
237 iter->end = g_utf8_next_char (iter->end);
238 continue;
239 }
240
241 if (width_iter_is_upright (ch) != iter->upright)
242 break;
243
244 iter->end = g_utf8_next_char (iter->end);
245 }
246}
247
248static void
249width_iter_init (PangoWidthIter *iter,
250 const char *text,
251 int length)
252{
253 iter->text_start = text;
254 iter->text_end = text + length;
255 iter->start = iter->end = text;
256
257 width_iter_next (iter);
258}
259
260static void
261width_iter_fini (PangoWidthIter *iter)
262{
263}
264
265/* }}} */
266/* {{{ Itemization */
267
268typedef struct _ItemizeState ItemizeState;
269
270
271typedef enum {
272 EMBEDDING_CHANGED = 1 << 0,
273 SCRIPT_CHANGED = 1 << 1,
274 LANG_CHANGED = 1 << 2,
275 FONT_CHANGED = 1 << 3,
276 DERIVED_LANG_CHANGED = 1 << 4,
277 WIDTH_CHANGED = 1 << 5,
278 EMOJI_CHANGED = 1 << 6,
279} ChangedFlags;
280
281
282struct _ItemizeState
283{
284 PangoContext *context;
285 const char *text;
286 const char *end;
287
288 const char *run_start;
289 const char *run_end;
290
291 GList *result;
292 PangoItem *item;
293
294 guint8 *embedding_levels;
295 int embedding_end_offset;
296 const char *embedding_end;
297 guint8 embedding;
298
299 PangoGravity gravity;
300 PangoGravityHint gravity_hint;
301 PangoGravity resolved_gravity;
302 PangoGravity font_desc_gravity;
303 gboolean centered_baseline;
304
305 PangoAttrIterator *attr_iter;
306 gboolean free_attr_iter;
307 const char *attr_end;
308 PangoFontDescription *font_desc;
309 PangoFontDescription *emoji_font_desc;
310 PangoLanguage *lang;
311 GSList *extra_attrs;
312 gboolean copy_extra_attrs;
313
314 ChangedFlags changed;
315
316 PangoScriptIter script_iter;
317 const char *script_end;
318 PangoScript script;
319
320 PangoWidthIter width_iter;
321 PangoEmojiIter emoji_iter;
322
323 PangoLanguage *derived_lang;
324
325 PangoFontset *current_fonts;
326 FontCache *cache;
327 PangoFont *base_font;
328 gboolean enable_fallback;
329
330 const char *first_space; /* first of a sequence of spaces we've seen */
331 int font_position; /* position of the current font in the fontset */
332};
333
334static void
335update_embedding_end (ItemizeState *state)
336{
337 state->embedding = state->embedding_levels[state->embedding_end_offset];
338 while (state->embedding_end < state->end &&
339 state->embedding_levels[state->embedding_end_offset] == state->embedding)
340 {
341 state->embedding_end_offset++;
342 state->embedding_end = g_utf8_next_char (state->embedding_end);
343 }
344
345 state->changed |= EMBEDDING_CHANGED;
346}
347
348static PangoAttribute *
349find_attribute (GSList *attr_list,
350 PangoAttrType type)
351{
352 GSList *node;
353
354 for (node = attr_list; node; node = node->next)
355 if (((PangoAttribute *) node->data)->klass->type == type)
356 return (PangoAttribute *) node->data;
357
358 return NULL;
359}
360
361static void
362update_attr_iterator (ItemizeState *state)
363{
364 PangoLanguage *old_lang;
365 PangoAttribute *attr;
366 int end_index;
367
368 pango_attr_iterator_range (iterator: state->attr_iter, NULL, end: &end_index);
369 if (end_index < state->end - state->text)
370 state->attr_end = state->text + end_index;
371 else
372 state->attr_end = state->end;
373
374 if (state->emoji_font_desc)
375 {
376 pango_font_description_free (desc: state->emoji_font_desc);
377 state->emoji_font_desc = NULL;
378 }
379
380 old_lang = state->lang;
381 if (state->font_desc)
382 pango_font_description_free (desc: state->font_desc);
383 state->font_desc = pango_font_description_copy_static (desc: state->context->font_desc);
384 pango_attr_iterator_get_font (iterator: state->attr_iter, desc: state->font_desc,
385 language: &state->lang, extra_attrs: &state->extra_attrs);
386 if (pango_font_description_get_set_fields (desc: state->font_desc) & PANGO_FONT_MASK_GRAVITY)
387 state->font_desc_gravity = pango_font_description_get_gravity (desc: state->font_desc);
388 else
389 state->font_desc_gravity = PANGO_GRAVITY_AUTO;
390
391 state->copy_extra_attrs = FALSE;
392
393 if (!state->lang)
394 state->lang = state->context->language;
395
396 attr = find_attribute (attr_list: state->extra_attrs, type: PANGO_ATTR_FALLBACK);
397 state->enable_fallback = (attr == NULL || ((PangoAttrInt *)attr)->value);
398
399 attr = find_attribute (attr_list: state->extra_attrs, type: PANGO_ATTR_GRAVITY);
400 state->gravity = attr == NULL ? PANGO_GRAVITY_AUTO : ((PangoAttrInt *)attr)->value;
401
402 attr = find_attribute (attr_list: state->extra_attrs, type: PANGO_ATTR_GRAVITY_HINT);
403 state->gravity_hint = attr == NULL ? state->context->gravity_hint : (PangoGravityHint)((PangoAttrInt *)attr)->value;
404
405 state->changed |= FONT_CHANGED;
406 if (state->lang != old_lang)
407 state->changed |= LANG_CHANGED;
408}
409
410static void
411update_end (ItemizeState *state)
412{
413 state->run_end = state->embedding_end;
414 if (state->attr_end < state->run_end)
415 state->run_end = state->attr_end;
416 if (state->script_end < state->run_end)
417 state->run_end = state->script_end;
418 if (state->width_iter.end < state->run_end)
419 state->run_end = state->width_iter.end;
420 if (state->emoji_iter.end < state->run_end)
421 state->run_end = state->emoji_iter.end;
422}
423
424
425static void
426itemize_state_init (ItemizeState *state,
427 PangoContext *context,
428 const char *text,
429 PangoDirection base_dir,
430 int start_index,
431 int length,
432 PangoAttrList *attrs,
433 PangoAttrIterator *cached_iter,
434 const PangoFontDescription *desc)
435{
436 state->context = context;
437 state->text = text;
438 state->end = text + start_index + length;
439
440 state->result = NULL;
441 state->item = NULL;
442
443 state->run_start = text + start_index;
444 state->changed = EMBEDDING_CHANGED | SCRIPT_CHANGED | LANG_CHANGED |
445 FONT_CHANGED | WIDTH_CHANGED | EMOJI_CHANGED;
446
447 /* First, apply the bidirectional algorithm to break
448 * the text into directional runs.
449 */
450 state->embedding_levels = pango_log2vis_get_embedding_levels (text: text + start_index, length, pbase_dir: &base_dir);
451
452 state->embedding_end_offset = 0;
453 state->embedding_end = text + start_index;
454 update_embedding_end (state);
455
456 state->gravity = PANGO_GRAVITY_AUTO;
457 state->centered_baseline = PANGO_GRAVITY_IS_VERTICAL (state->context->resolved_gravity);
458 state->gravity_hint = state->context->gravity_hint;
459 state->resolved_gravity = PANGO_GRAVITY_AUTO;
460
461 /* Initialize the attribute iterator
462 */
463 if (cached_iter)
464 {
465 state->attr_iter = cached_iter;
466 state->free_attr_iter = FALSE;
467 }
468 else if (attrs)
469 {
470 state->attr_iter = pango_attr_list_get_iterator (list: attrs);
471 state->free_attr_iter = TRUE;
472 }
473 else
474 {
475 state->attr_iter = NULL;
476 state->free_attr_iter = FALSE;
477 }
478
479 state->emoji_font_desc = NULL;
480 if (state->attr_iter)
481 {
482 state->font_desc = NULL;
483 state->lang = NULL;
484
485 pango_attr_iterator_advance (iterator: state->attr_iter, index: start_index);
486 update_attr_iterator (state);
487 }
488 else
489 {
490 state->font_desc = pango_font_description_copy_static (desc: desc ? desc : state->context->font_desc);
491 state->lang = state->context->language;
492 state->extra_attrs = NULL;
493 state->copy_extra_attrs = FALSE;
494
495 state->attr_end = state->end;
496 state->enable_fallback = TRUE;
497 }
498
499 /* Initialize the script iterator
500 */
501 _pango_script_iter_init (iter: &state->script_iter, text: text + start_index, length);
502 pango_script_iter_get_range (iter: &state->script_iter, NULL,
503 end: &state->script_end, script: &state->script);
504
505 width_iter_init (iter: &state->width_iter, text: text + start_index, length);
506 _pango_emoji_iter_init (iter: &state->emoji_iter, text: text + start_index, length);
507
508 if (!PANGO_GRAVITY_IS_VERTICAL (state->context->resolved_gravity))
509 state->width_iter.end = state->end;
510 else if (state->emoji_iter.is_emoji)
511 state->width_iter.end = MAX (state->width_iter.end, state->emoji_iter.end);
512
513 update_end (state);
514
515 if (pango_font_description_get_set_fields (desc: state->font_desc) & PANGO_FONT_MASK_GRAVITY)
516 state->font_desc_gravity = pango_font_description_get_gravity (desc: state->font_desc);
517 else
518 state->font_desc_gravity = PANGO_GRAVITY_AUTO;
519
520 state->derived_lang = NULL;
521 state->current_fonts = NULL;
522 state->cache = NULL;
523 state->base_font = NULL;
524 state->first_space = NULL;
525 state->font_position = 0xffff;
526}
527
528static gboolean
529itemize_state_next (ItemizeState *state)
530{
531 if (state->run_end == state->end)
532 return FALSE;
533
534 state->changed = 0;
535
536 state->run_start = state->run_end;
537
538 if (state->run_end == state->embedding_end)
539 {
540 update_embedding_end (state);
541 }
542
543 if (state->run_end == state->attr_end)
544 {
545 pango_attr_iterator_next (iterator: state->attr_iter);
546 update_attr_iterator (state);
547 }
548
549 if (state->run_end == state->script_end)
550 {
551 pango_script_iter_next (iter: &state->script_iter);
552 pango_script_iter_get_range (iter: &state->script_iter, NULL,
553 end: &state->script_end, script: &state->script);
554 state->changed |= SCRIPT_CHANGED;
555 }
556 if (state->run_end == state->emoji_iter.end)
557 {
558 _pango_emoji_iter_next (iter: &state->emoji_iter);
559 state->changed |= EMOJI_CHANGED;
560
561 if (state->emoji_iter.is_emoji)
562 state->width_iter.end = MAX (state->width_iter.end, state->emoji_iter.end);
563 }
564 if (state->run_end == state->width_iter.end)
565 {
566 width_iter_next (iter: &state->width_iter);
567 state->changed |= WIDTH_CHANGED;
568 }
569
570 update_end (state);
571
572 return TRUE;
573}
574
575static GSList *
576copy_attr_slist (GSList *attr_slist)
577{
578 GSList *new_list = NULL;
579 GSList *l;
580
581 for (l = attr_slist; l; l = l->next)
582 new_list = g_slist_prepend (list: new_list, data: pango_attribute_copy (attr: l->data));
583
584 return g_slist_reverse (list: new_list);
585}
586
587static void
588itemize_state_fill_font (ItemizeState *state,
589 PangoFont *font)
590{
591 GList *l;
592
593 for (l = state->result; l; l = l->next)
594 {
595 PangoItem *item = l->data;
596 if (item->analysis.font)
597 break;
598 if (font)
599 item->analysis.font = g_object_ref (font);
600 }
601}
602
603static void
604itemize_state_add_character (ItemizeState *state,
605 PangoFont *font,
606 int font_position,
607 gboolean force_break,
608 const char *pos,
609 gboolean is_space)
610{
611 const char *first_space = state->first_space;
612 int n_spaces = 0;
613
614 if (is_space)
615 {
616 if (state->first_space == NULL)
617 state->first_space = pos;
618 }
619 else
620 state->first_space = NULL;
621
622 if (state->item)
623 {
624 if (!state->item->analysis.font && font)
625 {
626 itemize_state_fill_font (state, font);
627 state->font_position = font_position;
628 }
629 else if (state->item->analysis.font && !font)
630 {
631 font = state->item->analysis.font;
632 font_position = state->font_position;
633 }
634
635 if (!force_break &&
636 state->item->analysis.font == font)
637 {
638 state->item->num_chars++;
639 return;
640 }
641
642 /* Font is changing, we are about to end the current item.
643 * If it ended in a sequence of spaces (but wasn't only spaces),
644 * check if we should move those spaces to the new item (since
645 * the font is less "fallback".
646 *
647 * See https://gitlab.gnome.org/GNOME/pango/-/issues/249
648 */
649 if (state->text + state->item->offset < first_space &&
650 font_position < state->font_position)
651 {
652 n_spaces = g_utf8_strlen (p: first_space, max: pos - first_space);
653 state->item->num_chars -= n_spaces;
654 pos = first_space;
655 }
656
657 state->item->length = (pos - state->text) - state->item->offset;
658 }
659
660 state->item = pango_item_new ();
661 state->item->offset = pos - state->text;
662 state->item->length = 0;
663 state->item->num_chars = n_spaces + 1;
664
665 if (font)
666 g_object_ref (font);
667 state->item->analysis.font = font;
668 state->font_position = font_position;
669
670 state->item->analysis.level = state->embedding;
671 state->item->analysis.gravity = state->resolved_gravity;
672
673 /* The level vs. gravity dance:
674 * - If gravity is SOUTH, leave level untouched.
675 * - If gravity is NORTH, step level one up, to
676 * not get mirrored upside-down text.
677 * - If gravity is EAST, step up to an even level, as
678 * it's a clockwise-rotated layout, so the rotated
679 * top is unrotated left.
680 * - If gravity is WEST, step up to an odd level, as
681 * it's a counter-clockwise-rotated layout, so the rotated
682 * top is unrotated right.
683 *
684 * A similar dance is performed in pango-layout.c:
685 * line_set_resolved_dir(). Keep in synch.
686 */
687 switch (state->item->analysis.gravity)
688 {
689 case PANGO_GRAVITY_SOUTH:
690 default:
691 break;
692 case PANGO_GRAVITY_NORTH:
693 state->item->analysis.level++;
694 break;
695 case PANGO_GRAVITY_EAST:
696 state->item->analysis.level += 1;
697 state->item->analysis.level &= ~1;
698 break;
699 case PANGO_GRAVITY_WEST:
700 state->item->analysis.level |= 1;
701 break;
702 }
703
704 state->item->analysis.flags |= state->centered_baseline ? PANGO_ANALYSIS_FLAG_CENTERED_BASELINE : 0;
705
706 state->item->analysis.script = state->script;
707 state->item->analysis.language = state->derived_lang;
708
709 if (state->copy_extra_attrs)
710 {
711 state->item->analysis.extra_attrs = copy_attr_slist (attr_slist: state->extra_attrs);
712 }
713 else
714 {
715 state->item->analysis.extra_attrs = state->extra_attrs;
716 state->copy_extra_attrs = TRUE;
717 }
718
719 state->result = g_list_prepend (list: state->result, data: state->item);
720}
721
722typedef struct {
723 PangoLanguage *lang;
724 gunichar wc;
725 PangoFont *font;
726 int position;
727} GetFontInfo;
728
729static gboolean
730get_font_foreach (PangoFontset *fontset,
731 PangoFont *font,
732 gpointer data)
733{
734 GetFontInfo *info = data;
735
736 if (G_UNLIKELY (!font))
737 return FALSE;
738
739 if (pango_font_has_char (font, wc: info->wc))
740 {
741 info->font = font;
742 return TRUE;
743 }
744
745 if (!fontset)
746 {
747 info->font = font;
748 return TRUE;
749 }
750
751 info->position++;
752
753 return FALSE;
754}
755
756static PangoFont *
757get_base_font (ItemizeState *state)
758{
759 if (!state->base_font)
760 state->base_font = pango_font_map_load_font (fontmap: state->context->font_map,
761 context: state->context,
762 desc: state->font_desc);
763 return state->base_font;
764}
765
766static gboolean
767get_font (ItemizeState *state,
768 gunichar wc,
769 PangoFont **font,
770 int *position)
771{
772 GetFontInfo info;
773
774 /* We'd need a separate cache when fallback is disabled, but since lookup
775 * with fallback disabled is faster anyways, we just skip caching
776 */
777 if (state->enable_fallback && font_cache_get (cache: state->cache, wc, font, position))
778 return TRUE;
779
780 info.lang = state->derived_lang;
781 info.wc = wc;
782 info.font = NULL;
783 info.position = 0;
784
785 if (state->enable_fallback)
786 pango_fontset_foreach (fontset: state->current_fonts, func: get_font_foreach, data: &info);
787
788 if (!info.font)
789 info.font = get_base_font (state);
790
791 *font = info.font;
792 *position = info.position;
793
794 /* skip caching if fallback disabled (see above) */
795 if (state->enable_fallback)
796 font_cache_insert (cache: state->cache, wc, font: *font, position: *position);
797
798 return TRUE;
799}
800
801static PangoLanguage *
802compute_derived_language (PangoLanguage *lang,
803 PangoScript script)
804{
805 PangoLanguage *derived_lang;
806
807 /* Make sure the language tag is consistent with the derived
808 * script. There is no point in marking up a section of
809 * Arabic text with the "en" language tag.
810 */
811 if (lang && pango_language_includes_script (language: lang, script))
812 derived_lang = lang;
813 else
814 {
815 derived_lang = pango_script_get_sample_language (script);
816 /* If we don't find a sample language for the script, we
817 * use a language tag that shouldn't actually be used
818 * anywhere. This keeps fontconfig (for the PangoFc*
819 * backend) from using the language tag to affect the
820 * sort order. I don't have a reference for 'xx' being
821 * safe here, though Keith Packard claims it is.
822 */
823 if (!derived_lang)
824 derived_lang = pango_language_from_string (language: "xx");
825 }
826
827 return derived_lang;
828}
829
830static void
831itemize_state_update_for_new_run (ItemizeState *state)
832{
833 /* This block should be moved to update_attr_iterator, but I'm too lazy to
834 * do it right now */
835 if (state->changed & (FONT_CHANGED | SCRIPT_CHANGED | WIDTH_CHANGED))
836 {
837 /* Font-desc gravity overrides everything */
838 if (state->font_desc_gravity != PANGO_GRAVITY_AUTO)
839 {
840 state->resolved_gravity = state->font_desc_gravity;
841 }
842 else
843 {
844 PangoGravity gravity = state->gravity;
845 PangoGravityHint gravity_hint = state->gravity_hint;
846
847 if (G_LIKELY (gravity == PANGO_GRAVITY_AUTO))
848 gravity = state->context->resolved_gravity;
849
850 state->resolved_gravity = pango_gravity_get_for_script_and_width (script: state->script,
851 wide: state->width_iter.upright,
852 base_gravity: gravity,
853 hint: gravity_hint);
854 }
855
856 if (state->font_desc_gravity != state->resolved_gravity)
857 {
858 pango_font_description_set_gravity (desc: state->font_desc, gravity: state->resolved_gravity);
859 state->changed |= FONT_CHANGED;
860 }
861 }
862
863 if (state->changed & (SCRIPT_CHANGED | LANG_CHANGED))
864 {
865 PangoLanguage *old_derived_lang = state->derived_lang;
866 state->derived_lang = compute_derived_language (lang: state->lang, script: state->script);
867 if (old_derived_lang != state->derived_lang)
868 state->changed |= DERIVED_LANG_CHANGED;
869 }
870
871 if (state->changed & (EMOJI_CHANGED))
872 {
873 state->changed |= FONT_CHANGED;
874 }
875
876 if (state->changed & (FONT_CHANGED | DERIVED_LANG_CHANGED) &&
877 state->current_fonts)
878 {
879 g_object_unref (object: state->current_fonts);
880 state->current_fonts = NULL;
881 state->cache = NULL;
882 }
883
884 if (!state->current_fonts)
885 {
886 gboolean is_emoji = state->emoji_iter.is_emoji;
887 if (is_emoji && !state->emoji_font_desc)
888 {
889 state->emoji_font_desc = pango_font_description_copy_static (desc: state->font_desc);
890 pango_font_description_set_family_static (desc: state->emoji_font_desc, family: "emoji");
891 }
892 state->current_fonts = pango_font_map_load_fontset (fontmap: state->context->font_map,
893 context: state->context,
894 desc: is_emoji ? state->emoji_font_desc : state->font_desc,
895 language: state->derived_lang);
896 state->cache = get_font_cache (fontset: state->current_fonts);
897 }
898
899 if ((state->changed & FONT_CHANGED) && state->base_font)
900 {
901 g_object_unref (object: state->base_font);
902 state->base_font = NULL;
903 }
904}
905
906/* We don't want space characters to affect font selection; in general,
907 * it's always wrong to select a font just to render a space.
908 *
909 * We assume that all fonts have the ASCII space, and for other space
910 * characters if they don't, HarfBuzz will compatibility-decompose them
911 * to ASCII space...
912 * See bugs #355987 and #701652.
913 *
914 * We don't want to change fonts just for variation selectors.
915 * See bug #781123.
916 *
917 * We don't want to change fonts for default ignorables such as Cf chars.
918 * Note that Cf chars in the Arabic block are visible and need to have
919 * a font, so we exclude.
920 *
921 * Finally, don't change fonts for line or paragraph separators.
922 *
923 * Note that we want spaces to use the 'better' font, comparing
924 * the font that is used before and after the space. This is handled
925 * in itemize_state_add_character().
926 */
927static gboolean
928consider_as_space (gunichar wc)
929{
930 GUnicodeType type = g_unichar_type (c: wc);
931 return type == G_UNICODE_CONTROL ||
932 (type == G_UNICODE_FORMAT && !((wc >= 0x600 && wc <= 0x06ff) || wc == 0x70f || wc == 0x8e2)) ||
933 type == G_UNICODE_SURROGATE ||
934 type == G_UNICODE_LINE_SEPARATOR ||
935 type == G_UNICODE_PARAGRAPH_SEPARATOR ||
936 (type == G_UNICODE_SPACE_SEPARATOR && wc != 0x1680u /* OGHAM SPACE MARK */) ||
937 (wc >= 0xfe00u && wc <= 0xfe0fu) ||
938 (wc >= 0xe0100u && wc <= 0xe01efu);
939}
940
941static void
942itemize_state_process_run (ItemizeState *state)
943{
944 const char *p;
945 gboolean last_was_forced_break = FALSE;
946 gboolean is_space;
947
948 /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 4.0;
949 * update this if that changes. */
950#define LINE_SEPARATOR 0x2028
951
952 itemize_state_update_for_new_run (state);
953
954 /* We should never get an empty run */
955 g_assert (state->run_end != state->run_start);
956
957 for (p = state->run_start;
958 p < state->run_end;
959 p = g_utf8_next_char (p))
960 {
961 gunichar wc = g_utf8_get_char (p);
962 gboolean is_forced_break = (wc == '\t' || wc == LINE_SEPARATOR);
963 PangoFont *font;
964 int font_position;
965
966 if (consider_as_space (wc))
967 {
968 font = NULL;
969 font_position = 0xffff;
970 is_space = TRUE;
971 }
972 else
973 {
974 get_font (state, wc, font: &font, position: &font_position);
975 is_space = FALSE;
976 }
977
978 itemize_state_add_character (state, font, font_position,
979 force_break: is_forced_break || last_was_forced_break,
980 pos: p,
981 is_space);
982
983 last_was_forced_break = is_forced_break;
984 }
985
986 /* Finish the final item from the current segment */
987 state->item->length = (p - state->text) - state->item->offset;
988 if (!state->item->analysis.font)
989 {
990 PangoFont *font;
991 int position;
992
993 if (G_UNLIKELY (!get_font (state, ' ', &font, &position)))
994 {
995 /* If no font was found, warn once per fontmap/script pair */
996 PangoFontMap *fontmap = state->context->font_map;
997 char *script_tag = g_strdup_printf (format: "g-unicode-script-%d", state->script);
998
999 if (!g_object_get_data (G_OBJECT (fontmap), key: script_tag))
1000 {
1001 g_warning ("failed to choose a font, expect ugly output. script='%d'",
1002 state->script);
1003
1004 g_object_set_data_full (G_OBJECT (fontmap), key: script_tag,
1005 GINT_TO_POINTER (1), NULL);
1006 }
1007
1008 g_free (mem: script_tag);
1009
1010 font = NULL;
1011 }
1012 itemize_state_fill_font (state, font);
1013 }
1014 state->item = NULL;
1015}
1016
1017static void
1018itemize_state_finish (ItemizeState *state)
1019{
1020 g_free (mem: state->embedding_levels);
1021 if (state->free_attr_iter)
1022 pango_attr_iterator_destroy (iterator: state->attr_iter);
1023 _pango_script_iter_fini (iter: &state->script_iter);
1024 pango_font_description_free (desc: state->font_desc);
1025 pango_font_description_free (desc: state->emoji_font_desc);
1026 width_iter_fini (iter: &state->width_iter);
1027 _pango_emoji_iter_fini (iter: &state->emoji_iter);
1028
1029 if (state->current_fonts)
1030 g_object_unref (object: state->current_fonts);
1031 if (state->base_font)
1032 g_object_unref (object: state->base_font);
1033}
1034
1035/* }}} */
1036/* {{{ Post-processing */
1037
1038 /* {{{ Handling font scale */
1039
1040typedef struct {
1041 PangoAttribute *attr;
1042 double scale;
1043} ScaleItem;
1044
1045static gboolean
1046collect_font_scale (PangoContext *context,
1047 GList **stack,
1048 PangoItem *item,
1049 PangoItem *prev,
1050 double *scale,
1051 gboolean *is_small_caps)
1052{
1053 gboolean retval = FALSE;
1054 GList *l;
1055
1056 for (GSList *l = item->analysis.extra_attrs; l; l = l->next)
1057 {
1058 PangoAttribute *attr = l->data;
1059
1060 if (attr->klass->type == PANGO_ATTR_FONT_SCALE)
1061 {
1062 if (attr->start_index == item->offset)
1063 {
1064 ScaleItem *entry;
1065 int y_scale;
1066 hb_position_t y_size;
1067 hb_position_t cap_height;
1068 hb_position_t x_height;
1069
1070 entry = g_new (ScaleItem, 1);
1071 entry->attr = attr;
1072 *stack = g_list_prepend (list: *stack, data: entry);
1073
1074 switch (((PangoAttrInt *)attr)->value)
1075 {
1076 case PANGO_FONT_SCALE_NONE:
1077 break;
1078 case PANGO_FONT_SCALE_SUPERSCRIPT:
1079 if (prev &&
1080 hb_ot_metrics_get_position (font: pango_font_get_hb_font (font: prev->analysis.font),
1081 metrics_tag: HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_SIZE,
1082 position: &y_size))
1083 {
1084 hb_font_get_scale (font: pango_font_get_hb_font (font: prev->analysis.font), NULL, y_scale: &y_scale);
1085 entry->scale = y_size / (double) y_scale;
1086 }
1087 else
1088 {
1089 entry->scale = 1 / 1.2;
1090 }
1091 break;
1092 case PANGO_FONT_SCALE_SUBSCRIPT:
1093 if (prev &&
1094 hb_ot_metrics_get_position (font: pango_font_get_hb_font (font: prev->analysis.font),
1095 metrics_tag: HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_SIZE,
1096 position: &y_size))
1097 {
1098 hb_font_get_scale (font: pango_font_get_hb_font (font: prev->analysis.font), NULL, y_scale: &y_scale);
1099 entry->scale = y_size / (double) y_scale;
1100 }
1101 else
1102 {
1103 entry->scale = 1 / 1.2;
1104 }
1105 break;
1106 case PANGO_FONT_SCALE_SMALL_CAPS:
1107 if (hb_ot_metrics_get_position (font: pango_font_get_hb_font (font: item->analysis.font),
1108 metrics_tag: HB_OT_METRICS_TAG_CAP_HEIGHT,
1109 position: &cap_height) &&
1110 hb_ot_metrics_get_position (font: pango_font_get_hb_font (font: item->analysis.font),
1111 metrics_tag: HB_OT_METRICS_TAG_X_HEIGHT,
1112 position: &x_height))
1113 {
1114 entry->scale = x_height / (double) cap_height;
1115 }
1116 else
1117 {
1118 entry->scale = 0.8;
1119 }
1120 break;
1121 default:
1122 g_assert_not_reached ();
1123 }
1124 }
1125 }
1126 }
1127
1128 *scale = 1.0;
1129 *is_small_caps = TRUE;
1130
1131 for (l = *stack; l; l = l->next)
1132 {
1133 ScaleItem *entry = l->data;
1134 *scale *= entry->scale;
1135 if (((PangoAttrInt *)entry->attr)->value != PANGO_FONT_SCALE_SMALL_CAPS)
1136 *is_small_caps = FALSE;
1137 retval = TRUE;
1138 }
1139
1140 l = *stack;
1141 while (l)
1142 {
1143 ScaleItem *entry = l->data;
1144 GList *next = l->next;
1145
1146 if (entry->attr->end_index == item->offset + item->length)
1147 {
1148 *stack = g_list_delete_link (list: *stack, link_: l);
1149 g_free (mem: entry);
1150 }
1151
1152 l = next;
1153 }
1154
1155 return retval;
1156}
1157
1158static void
1159apply_scale_to_item (PangoContext *context,
1160 PangoItem *item,
1161 double scale,
1162 gboolean is_small_caps)
1163{
1164 PangoFontDescription *desc;
1165 double size;
1166
1167 if (!item->analysis.font)
1168 return;
1169
1170 if (is_small_caps)
1171 pango_analysis_set_size_font (analysis: &item->analysis, font: item->analysis.font);
1172
1173 desc = pango_font_describe (font: item->analysis.font);
1174 size = scale * pango_font_description_get_size (desc);
1175
1176 if (pango_font_description_get_size_is_absolute (desc))
1177 pango_font_description_set_absolute_size (desc, size);
1178 else
1179 pango_font_description_set_size (desc, size);
1180
1181 g_object_unref (object: item->analysis.font);
1182 item->analysis.font = pango_font_map_load_font (fontmap: context->font_map, context, desc);
1183
1184 pango_font_description_free (desc);
1185}
1186
1187static void
1188apply_font_scale (PangoContext *context,
1189 GList *items)
1190{
1191 PangoItem *prev = NULL;
1192 GList *stack = NULL;
1193
1194 for (GList *l = items; l; l = l->next)
1195 {
1196 PangoItem *item = l->data;
1197 double scale;
1198 gboolean is_small_caps;
1199
1200 if (collect_font_scale (context, stack: &stack, item, prev, scale: &scale, is_small_caps: &is_small_caps))
1201 apply_scale_to_item (context, item, scale, is_small_caps);
1202
1203 prev = item;
1204 }
1205
1206 if (stack != NULL)
1207 {
1208 g_warning ("Leftover font scales");
1209 g_list_free_full (list: stack, free_func: g_free);
1210 }
1211}
1212
1213/* }}} */
1214/* {{{ Handling Casing variants */
1215
1216static gboolean
1217all_features_supported (PangoItem *item,
1218 hb_tag_t *features,
1219 guint n_features)
1220{
1221 hb_font_t *font = pango_font_get_hb_font (font: item->analysis.font);
1222 hb_face_t *face = hb_font_get_face (font);
1223 hb_script_t script;
1224 hb_language_t language;
1225 guint script_count = HB_OT_MAX_TAGS_PER_SCRIPT;
1226 hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT];
1227 hb_tag_t chosen_script;
1228 guint language_count = HB_OT_MAX_TAGS_PER_LANGUAGE;
1229 hb_tag_t language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE];
1230 guint script_index, language_index;
1231 guint index;
1232
1233 script = g_unicode_script_to_iso15924 (script: item->analysis.script);
1234 language = hb_language_from_string (pango_language_to_string (item->analysis.language), len: -1);
1235
1236 hb_ot_tags_from_script_and_language (script, language,
1237 script_count: &script_count, script_tags,
1238 language_count: &language_count, language_tags);
1239 hb_ot_layout_table_select_script (face, HB_OT_TAG_GSUB,
1240 script_count, script_tags,
1241 script_index: &script_index,
1242 chosen_script: &chosen_script);
1243 hb_ot_layout_script_select_language (face, HB_OT_TAG_GSUB,
1244 script_index,
1245 language_count, language_tags,
1246 language_index: &language_index);
1247
1248 for (int i = 0; i < n_features; i++)
1249 {
1250 if (!hb_ot_layout_language_find_feature (face, HB_OT_TAG_GSUB,
1251 script_index, language_index,
1252 feature_tag: features[i],
1253 feature_index: &index))
1254 return FALSE;
1255 }
1256
1257 return TRUE;
1258}
1259
1260static gboolean
1261variant_supported (PangoItem *item,
1262 PangoVariant variant)
1263{
1264 hb_tag_t features[2];
1265 guint num_features = 0;
1266
1267 switch (variant)
1268 {
1269 case PANGO_VARIANT_NORMAL:
1270 case PANGO_VARIANT_TITLE_CAPS:
1271 return TRUE;
1272 case PANGO_VARIANT_SMALL_CAPS:
1273 features[num_features++] = HB_TAG ('s', 'm', 'c', 'p');
1274 break;
1275 case PANGO_VARIANT_ALL_SMALL_CAPS:
1276 features[num_features++] = HB_TAG ('s', 'm', 'c', 'p');
1277 features[num_features++] = HB_TAG ('c', '2', 's', 'c');
1278 break;
1279 case PANGO_VARIANT_PETITE_CAPS:
1280 features[num_features++] = HB_TAG ('p', 'c', 'a', 'p');
1281 break;
1282 case PANGO_VARIANT_ALL_PETITE_CAPS:
1283 features[num_features++] = HB_TAG ('p', 'c', 'a', 'p');
1284 features[num_features++] = HB_TAG ('c', '2', 'p', 'c');
1285 break;
1286 case PANGO_VARIANT_UNICASE:
1287 features[num_features++] = HB_TAG ('u', 'n', 'i', 'c');
1288 break;
1289 default:
1290 g_assert_not_reached ();
1291 }
1292
1293 return all_features_supported (item, features, n_features: num_features);
1294}
1295
1296static PangoVariant
1297get_font_variant (PangoItem *item)
1298{
1299 if (item->analysis.font)
1300 return pango_font_get_variant (font: item->analysis.font);
1301
1302 return PANGO_VARIANT_NORMAL;
1303}
1304
1305static PangoTextTransform
1306find_text_transform (const PangoAnalysis *analysis)
1307{
1308 GSList *l;
1309 PangoTextTransform transform = PANGO_TEXT_TRANSFORM_NONE;
1310
1311 for (l = analysis->extra_attrs; l; l = l->next)
1312 {
1313 PangoAttribute *attr = l->data;
1314
1315 if (attr->klass->type == PANGO_ATTR_TEXT_TRANSFORM)
1316 transform = (PangoTextTransform) ((PangoAttrInt*)attr)->value;
1317 }
1318
1319 return transform;
1320}
1321
1322/* Split list_item into upper- and lowercase runs, and
1323 * add font scale and text transform attributes to make
1324 * them be appear according to variant. The log_attrs are
1325 * needed for taking text transforms into account when
1326 * determining the case of characters int he run.
1327 */
1328static void
1329split_item_for_variant (const char *text,
1330 PangoLogAttr *log_attrs,
1331 PangoVariant variant,
1332 GList *list_item)
1333{
1334 PangoItem *item = list_item->data;
1335 const char *start, *end;
1336 const char *p, *p0;
1337 gunichar wc;
1338 PangoTextTransform transform = PANGO_TEXT_TRANSFORM_NONE;
1339 PangoFontScale lowercase_scale = PANGO_FONT_SCALE_NONE;
1340 PangoFontScale uppercase_scale = PANGO_FONT_SCALE_NONE;
1341 PangoTextTransform item_transform;
1342 gboolean is_word_start;
1343 int offset;
1344
1345 switch (variant)
1346 {
1347 case PANGO_VARIANT_ALL_SMALL_CAPS:
1348 case PANGO_VARIANT_ALL_PETITE_CAPS:
1349 uppercase_scale = PANGO_FONT_SCALE_SMALL_CAPS;
1350 G_GNUC_FALLTHROUGH;
1351 case PANGO_VARIANT_SMALL_CAPS:
1352 case PANGO_VARIANT_PETITE_CAPS:
1353 transform = PANGO_TEXT_TRANSFORM_UPPERCASE;
1354 lowercase_scale = PANGO_FONT_SCALE_SMALL_CAPS;
1355 break;
1356 case PANGO_VARIANT_UNICASE:
1357 uppercase_scale = PANGO_FONT_SCALE_SMALL_CAPS;
1358 break;
1359 case PANGO_VARIANT_NORMAL:
1360 case PANGO_VARIANT_TITLE_CAPS:
1361 default:
1362 g_assert_not_reached ();
1363 }
1364
1365 item_transform = find_text_transform (analysis: &item->analysis);
1366
1367 start = text + item->offset;
1368 end = start + item->length;
1369 offset = ((PangoItemPrivate *)item)->char_offset;
1370
1371 p = start;
1372 while (p < end)
1373 {
1374 p0 = p;
1375 wc = g_utf8_get_char (p);
1376 is_word_start = log_attrs && log_attrs[offset].is_word_start;
1377 while (p < end && (item_transform == PANGO_TEXT_TRANSFORM_LOWERCASE ||
1378 consider_as_space (wc) ||
1379 (g_unichar_islower (c: wc) &&
1380 !(item_transform == PANGO_TEXT_TRANSFORM_UPPERCASE ||
1381 (item_transform == PANGO_TEXT_TRANSFORM_CAPITALIZE && is_word_start)))))
1382 {
1383 p = g_utf8_next_char (p);
1384 wc = g_utf8_get_char (p);
1385 offset++;
1386 is_word_start = log_attrs && log_attrs[offset].is_word_start;
1387 }
1388
1389 if (p0 < p)
1390 {
1391 PangoItem *new_item;
1392 PangoAttribute *attr;
1393
1394 /* p0 .. p is a lowercase segment */
1395 if (p < end)
1396 {
1397 new_item = pango_item_split (orig: item, split_index: p - p0, split_offset: g_utf8_strlen (p: p0, max: p - p0));
1398 list_item->data = new_item;
1399 list_item = g_list_insert_before (list: list_item, sibling: list_item->next, data: item);
1400 list_item = list_item->next;
1401 }
1402 else
1403 {
1404 new_item = item;
1405 }
1406
1407 if (transform != PANGO_TEXT_TRANSFORM_NONE)
1408 {
1409 attr = pango_attr_text_transform_new (transform);
1410 attr->start_index = new_item->offset;
1411 attr->end_index = new_item->offset + new_item->length;
1412 new_item->analysis.extra_attrs = g_slist_append (list: new_item->analysis.extra_attrs, data: attr);
1413 }
1414
1415 if (lowercase_scale != PANGO_FONT_SCALE_NONE)
1416 {
1417 attr = pango_attr_font_scale_new (scale: lowercase_scale);
1418 attr->start_index = new_item->offset;
1419 attr->end_index = new_item->offset + new_item->length;
1420 new_item->analysis.extra_attrs = g_slist_append (list: new_item->analysis.extra_attrs, data: attr);
1421 }
1422 }
1423
1424 p0 = p;
1425 wc = g_utf8_get_char (p);
1426 is_word_start = log_attrs && log_attrs[offset].is_word_start;
1427 while (p < end && (item_transform == PANGO_TEXT_TRANSFORM_UPPERCASE ||
1428 consider_as_space (wc) ||
1429 !(item_transform == PANGO_TEXT_TRANSFORM_LOWERCASE || g_unichar_islower (c: wc)) ||
1430 (item_transform == PANGO_TEXT_TRANSFORM_CAPITALIZE && is_word_start)))
1431 {
1432 p = g_utf8_next_char (p);
1433 wc = g_utf8_get_char (p);
1434 offset++;
1435 is_word_start = log_attrs && log_attrs[offset].is_word_start;
1436 }
1437
1438 if (p0 < p)
1439 {
1440 PangoItem *new_item;
1441 PangoAttribute *attr;
1442
1443 /* p0 .. p is a uppercase segment */
1444 if (p < end)
1445 {
1446 new_item = pango_item_split (orig: item, split_index: p - p0, split_offset: g_utf8_strlen (p: p0, max: p - p0));
1447 list_item->data = new_item;
1448 list_item = g_list_insert_before (list: list_item, sibling: list_item->next, data: item);
1449 list_item = list_item->next;
1450 }
1451 else
1452 {
1453 new_item = item;
1454 }
1455
1456 if (uppercase_scale != PANGO_FONT_SCALE_NONE)
1457 {
1458 attr = pango_attr_font_scale_new (scale: uppercase_scale);
1459 attr->start_index = new_item->offset;
1460 attr->end_index = new_item->offset + new_item->length;
1461 new_item->analysis.extra_attrs = g_slist_append (list: new_item->analysis.extra_attrs, data: attr);
1462 }
1463 }
1464 }
1465}
1466
1467static void
1468handle_variants_for_item (const char *text,
1469 PangoLogAttr *log_attrs,
1470 GList *l)
1471{
1472 PangoItem *item = l->data;
1473 PangoVariant variant;
1474
1475 variant = get_font_variant (item);
1476 if (!variant_supported (item, variant))
1477 split_item_for_variant (text, log_attrs, variant, list_item: l);
1478}
1479
1480static void
1481handle_variants (const char *text,
1482 PangoLogAttr *log_attrs,
1483 GList *items)
1484{
1485 GList *next;
1486
1487 for (GList *l = items; l; l = next)
1488 {
1489 next = l->next;
1490 handle_variants_for_item (text, log_attrs, l);
1491 }
1492}
1493
1494/* }}} */
1495
1496static GList *
1497reorder_items (PangoContext *context,
1498 GList *items)
1499{
1500 int char_offset = 0;
1501
1502 items = g_list_reverse (list: items);
1503
1504 /* Also cmpute the char offset for each item here */
1505 for (GList *l = items; l; l = l->next)
1506 {
1507 PangoItemPrivate *item = l->data;
1508 item->char_offset = char_offset;
1509 char_offset += item->num_chars;
1510 }
1511
1512 return items;
1513}
1514
1515static GList *
1516post_process_items (PangoContext *context,
1517 const char *text,
1518 PangoLogAttr *log_attrs,
1519 GList *items)
1520{
1521 handle_variants (text, log_attrs, items);
1522 apply_font_scale (context, items);
1523
1524 return items;
1525}
1526
1527/* }}} */
1528/* {{{ Private API */
1529
1530/* Like pango_itemize_with_base_dir, but takes a font description.
1531 * In contrast to pango_itemize_with_base_dir, this function does
1532 * not call pango_itemize_post_process_items, so you need to do that
1533 * separately, after applying attributes that affect segmentation and
1534 * computing the log attrs.
1535 */
1536GList *
1537pango_itemize_with_font (PangoContext *context,
1538 PangoDirection base_dir,
1539 const char *text,
1540 int start_index,
1541 int length,
1542 PangoAttrList *attrs,
1543 PangoAttrIterator *cached_iter,
1544 const PangoFontDescription *desc)
1545{
1546 ItemizeState state;
1547
1548 g_return_val_if_fail (context->font_map != NULL, NULL);
1549
1550 if (length == 0 || g_utf8_get_char (p: text + start_index) == '\0')
1551 return NULL;
1552
1553 itemize_state_init (state: &state, context, text, base_dir, start_index, length,
1554 attrs, cached_iter, desc);
1555
1556 do
1557 itemize_state_process_run (state: &state);
1558 while (itemize_state_next (state: &state));
1559
1560 itemize_state_finish (state: &state);
1561
1562 return reorder_items (context, items: state.result);
1563}
1564
1565/* Apply post-processing steps that may require log attrs.
1566 */
1567GList *
1568pango_itemize_post_process_items (PangoContext *context,
1569 const char *text,
1570 PangoLogAttr *log_attrs,
1571 GList *items)
1572{
1573 return post_process_items (context, text, log_attrs, items);
1574}
1575
1576/* }}} */
1577/* {{{ Public API */
1578
1579/**
1580 * pango_itemize_with_base_dir:
1581 * @context: a structure holding information that affects
1582 * the itemization process.
1583 * @base_dir: base direction to use for bidirectional processing
1584 * @text: the text to itemize.
1585 * @start_index: first byte in @text to process
1586 * @length: the number of bytes (not characters) to process
1587 * after @start_index. This must be >= 0.
1588 * @attrs: the set of attributes that apply to @text.
1589 * @cached_iter: (nullable): Cached attribute iterator
1590 *
1591 * Like `pango_itemize()`, but with an explicitly specified base direction.
1592 *
1593 * The base direction is used when computing bidirectional levels.
1594 * [func@itemize] gets the base direction from the `PangoContext`
1595 * (see [method@Pango.Context.set_base_dir]).
1596 *
1597 * Return value: (transfer full) (element-type Pango.Item): a `GList` of
1598 * [struct@Pango.Item] structures. The items should be freed using
1599 * [method@Pango.Item.free] probably in combination with [func@GLib.List.free_full].
1600 *
1601 * Since: 1.4
1602 */
1603GList *
1604pango_itemize_with_base_dir (PangoContext *context,
1605 PangoDirection base_dir,
1606 const char *text,
1607 int start_index,
1608 int length,
1609 PangoAttrList *attrs,
1610 PangoAttrIterator *cached_iter)
1611{
1612 GList *items;
1613
1614 g_return_val_if_fail (context != NULL, NULL);
1615 g_return_val_if_fail (start_index >= 0, NULL);
1616 g_return_val_if_fail (length >= 0, NULL);
1617 g_return_val_if_fail (length == 0 || text != NULL, NULL);
1618
1619 items = pango_itemize_with_font (context, base_dir,
1620 text, start_index, length,
1621 attrs, cached_iter,
1622 NULL);
1623
1624 return pango_itemize_post_process_items (context, text, NULL, items);
1625}
1626
1627/**
1628 * pango_itemize:
1629 * @context: a structure holding information that affects
1630 * the itemization process.
1631 * @text: the text to itemize. Must be valid UTF-8
1632 * @start_index: first byte in @text to process
1633 * @length: the number of bytes (not characters) to process
1634 * after @start_index. This must be >= 0.
1635 * @attrs: the set of attributes that apply to @text.
1636 * @cached_iter: (nullable): Cached attribute iterator
1637 *
1638 * Breaks a piece of text into segments with consistent directional
1639 * level and font.
1640 *
1641 * Each byte of @text will be contained in exactly one of the items in the
1642 * returned list; the generated list of items will be in logical order (the
1643 * start offsets of the items are ascending).
1644 *
1645 * @cached_iter should be an iterator over @attrs currently positioned
1646 * at a range before or containing @start_index; @cached_iter will be
1647 * advanced to the range covering the position just after
1648 * @start_index + @length. (i.e. if itemizing in a loop, just keep passing
1649 * in the same @cached_iter).
1650 *
1651 * Return value: (transfer full) (element-type Pango.Item): a `GList` of
1652 * [struct@Pango.Item] structures. The items should be freed using
1653 * [method@Pango.Item.free] in combination with [func@GLib.List.free_full].
1654 */
1655GList *
1656pango_itemize (PangoContext *context,
1657 const char *text,
1658 int start_index,
1659 int length,
1660 PangoAttrList *attrs,
1661 PangoAttrIterator *cached_iter)
1662{
1663 g_return_val_if_fail (context != NULL, NULL);
1664 g_return_val_if_fail (start_index >= 0, NULL);
1665 g_return_val_if_fail (length >= 0, NULL);
1666 g_return_val_if_fail (length == 0 || text != NULL, NULL);
1667
1668 return pango_itemize_with_base_dir (context, base_dir: context->base_dir,
1669 text, start_index, length,
1670 attrs, cached_iter);
1671}
1672
1673/* }}} */
1674
1675/* vim:set foldmethod=marker expandtab: */
1676

source code of gtk/subprojects/pango/pango/itemize.c