Upload tizen 2.0 beta source
[external/pango1.0.git] / pango / pango-context.c
1 /* Pango
2  * pango-context.c: Contexts for itemization and shaping
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.h"
27 #include "pango-impl-utils.h"
28
29 #include "pango-engine.h"
30 #include "pango-engine-private.h"
31 #include "pango-modules.h"
32 #include "pango-script-private.h"
33
34 struct _PangoContext
35 {
36   GObject parent_instance;
37
38   PangoLanguage *set_language;
39   PangoLanguage *language;
40   PangoDirection base_dir;
41   PangoGravity base_gravity;
42   PangoGravity resolved_gravity;
43   PangoGravityHint gravity_hint;
44
45   PangoFontDescription *font_desc;
46
47   PangoMatrix *matrix;
48
49   PangoFontMap *font_map;
50 };
51
52 struct _PangoContextClass
53 {
54   GObjectClass parent_class;
55
56 };
57
58 static void pango_context_finalize    (GObject           *object);
59
60 G_DEFINE_TYPE (PangoContext, pango_context, G_TYPE_OBJECT)
61
62 static void
63 pango_context_init (PangoContext *context)
64 {
65   context->base_dir = PANGO_DIRECTION_WEAK_LTR;
66   context->resolved_gravity = context->base_gravity = PANGO_GRAVITY_SOUTH;
67   context->gravity_hint = PANGO_GRAVITY_HINT_NATURAL;
68
69   context->set_language = NULL;
70   context->language = pango_language_get_default ();
71   context->font_map = NULL;
72
73   context->font_desc = pango_font_description_new ();
74   pango_font_description_set_family_static (context->font_desc, "serif");
75   pango_font_description_set_style (context->font_desc, PANGO_STYLE_NORMAL);
76   pango_font_description_set_variant (context->font_desc, PANGO_VARIANT_NORMAL);
77   pango_font_description_set_weight (context->font_desc, PANGO_WEIGHT_NORMAL);
78   pango_font_description_set_stretch (context->font_desc, PANGO_STRETCH_NORMAL);
79   pango_font_description_set_size (context->font_desc, 12 * PANGO_SCALE);
80 }
81
82 static void
83 pango_context_class_init (PangoContextClass *klass)
84 {
85   GObjectClass *object_class = G_OBJECT_CLASS (klass);
86
87   object_class->finalize = pango_context_finalize;
88 }
89
90 static void
91 pango_context_finalize (GObject *object)
92 {
93   PangoContext *context;
94
95   context = PANGO_CONTEXT (object);
96
97   if (context->font_map)
98     g_object_unref (context->font_map);
99
100   pango_font_description_free (context->font_desc);
101   if (context->matrix)
102     pango_matrix_free (context->matrix);
103
104   G_OBJECT_CLASS (pango_context_parent_class)->finalize (object);
105 }
106
107
108 /**
109  * pango_context_new:
110  *
111  * Creates a new #PangoContext initialized to default values.
112  *
113  * This function is not particularly useful as it should always
114  * be followed by a pango_context_set_font_map() call, and the
115  * function pango_font_map_create_context() does these two steps
116  * together and hence users are recommended to use that.
117  *
118  * If you are using Pango as part of a higher-level system,
119  * that system may have it's own way of create a #PangoContext.
120  * For instance, the GTK+ toolkit has, among others,
121  * gdk_pango_context_get_for_screen(), and
122  * gtk_widget_get_pango_context().  Use those instead.
123  *
124  * Return value: the newly allocated #PangoContext, which should
125  *               be freed with g_object_unref().
126  **/
127 PangoContext *
128 pango_context_new (void)
129 {
130   PangoContext *context;
131
132   context = g_object_new (PANGO_TYPE_CONTEXT, NULL);
133
134   return context;
135 }
136
137 static void
138 update_resolved_gravity (PangoContext *context)
139 {
140   if (context->base_gravity == PANGO_GRAVITY_AUTO)
141     context->resolved_gravity = pango_gravity_get_for_matrix (context->matrix);
142   else
143     context->resolved_gravity = context->base_gravity;
144 }
145
146 /**
147  * pango_context_set_matrix:
148  * @context: a #PangoContext
149  * @matrix: a #PangoMatrix, or %NULL to unset any existing matrix.
150  *  (No matrix set is the same as setting the identity matrix.)
151  *
152  * Sets the transformation matrix that will be applied when rendering
153  * with this context. Note that reported metrics are in the user space
154  * coordinates before the application of the matrix, not device-space
155  * coordinates after the application of the matrix. So, they don't scale
156  * with the matrix, though they may change slightly for different
157  * matrices, depending on how the text is fit to the pixel grid.
158  *
159  * Since: 1.6
160  **/
161 void
162 pango_context_set_matrix (PangoContext       *context,
163                           const PangoMatrix  *matrix)
164 {
165   g_return_if_fail (PANGO_IS_CONTEXT (context));
166
167   if (context->matrix)
168     pango_matrix_free (context->matrix);
169   if (matrix)
170     context->matrix = pango_matrix_copy (matrix);
171   else
172     context->matrix = NULL;
173
174   update_resolved_gravity (context);
175 }
176
177 /**
178  * pango_context_get_matrix:
179  * @context: a #PangoContext
180  *
181  * Gets the transformation matrix that will be applied when
182  * rendering with this context. See pango_context_set_matrix().
183  *
184  * Return value: the matrix, or %NULL if no matrix has been set
185  *  (which is the same as the identity matrix). The returned
186  *  matrix is owned by Pango and must not be modified or
187  *  freed.
188  *
189  * Since: 1.6
190  **/
191 G_CONST_RETURN PangoMatrix *
192 pango_context_get_matrix (PangoContext *context)
193 {
194   g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
195
196   return context->matrix;
197 }
198
199 /**
200  * pango_context_set_font_map:
201  * @context: a #PangoContext
202  * @font_map: the #PangoFontMap to set.
203  *
204  * Sets the font map to be searched when fonts are looked-up in this context.
205  * This is only for internal use by Pango backends, a #PangoContext obtained
206  * via one of the recommended methods should already have a suitable font map.
207  **/
208 void
209 pango_context_set_font_map (PangoContext *context,
210                             PangoFontMap *font_map)
211 {
212   g_return_if_fail (PANGO_IS_CONTEXT (context));
213   g_return_if_fail (!font_map || PANGO_IS_FONT_MAP (font_map));
214
215   if (font_map)
216     g_object_ref (font_map);
217
218   if (context->font_map)
219     g_object_unref (context->font_map);
220
221   context->font_map = font_map;
222 }
223
224 /**
225  * pango_context_get_font_map:
226  * @context: a #PangoContext
227  *
228  * Gets the #PangoFontmap used to look up fonts for this context.
229  *
230  * Return value: the font map for the #PangoContext. This value
231  *  is owned by Pango and should not be unreferenced.
232  *
233  * Since: 1.6
234  **/
235 PangoFontMap *
236 pango_context_get_font_map (PangoContext *context)
237 {
238   g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
239
240   return context->font_map;
241 }
242
243 /**
244  * pango_context_list_families:
245  * @context: a #PangoContext
246  * @families: location to store a pointer to an array of #PangoFontFamily *.
247  *            This array should be freed with g_free().
248  * @n_families: location to store the number of elements in @descs
249  *
250  * List all families for a context.
251  **/
252 void
253 pango_context_list_families (PangoContext          *context,
254                              PangoFontFamily     ***families,
255                              int                   *n_families)
256 {
257   g_return_if_fail (context != NULL);
258   g_return_if_fail (families == NULL || n_families != NULL);
259
260   if (n_families == NULL)
261     return;
262
263   if (context->font_map == NULL)
264     {
265       *n_families = 0;
266       if (families)
267         *families = NULL;
268
269       return;
270     }
271   else
272     pango_font_map_list_families (context->font_map, families, n_families);
273 }
274
275 /**
276  * pango_context_load_font:
277  * @context: a #PangoContext
278  * @desc: a #PangoFontDescription describing the font to load
279  *
280  * Loads the font in one of the fontmaps in the context
281  * that is the closest match for @desc.
282  *
283  * Returns: the font loaded, or %NULL if no font matched.
284  **/
285 PangoFont *
286 pango_context_load_font (PangoContext               *context,
287                          const PangoFontDescription *desc)
288 {
289   g_return_val_if_fail (context != NULL, NULL);
290
291   return pango_font_map_load_font (context->font_map, context, desc);
292 }
293
294 /**
295  * pango_context_load_fontset:
296  * @context: a #PangoContext
297  * @desc: a #PangoFontDescription describing the fonts to load
298  * @language: a #PangoLanguage the fonts will be used for
299  *
300  * Load a set of fonts in the context that can be used to render
301  * a font matching @desc.
302  *
303  * Returns: the fontset, or %NULL if no font matched.
304  **/
305 PangoFontset *
306 pango_context_load_fontset (PangoContext               *context,
307                             const PangoFontDescription *desc,
308                             PangoLanguage             *language)
309 {
310   g_return_val_if_fail (context != NULL, NULL);
311
312   return pango_font_map_load_fontset (context->font_map, context, desc, language);
313 }
314
315 /**
316  * pango_context_set_font_description:
317  * @context: a #PangoContext
318  * @desc: the new pango font description
319  *
320  * Set the default font description for the context
321  **/
322 void
323 pango_context_set_font_description (PangoContext               *context,
324                                     const PangoFontDescription *desc)
325 {
326   g_return_if_fail (context != NULL);
327   g_return_if_fail (desc != NULL);
328
329   pango_font_description_free (context->font_desc);
330   context->font_desc = pango_font_description_copy (desc);
331 }
332
333 /**
334  * pango_context_get_font_description:
335  * @context: a #PangoContext
336  *
337  * Retrieve the default font description for the context.
338  *
339  * Return value: a pointer to the context's default font description.
340  *               This value must not be modified or freed.
341  **/
342 PangoFontDescription *
343 pango_context_get_font_description (PangoContext *context)
344 {
345   g_return_val_if_fail (context != NULL, NULL);
346
347   return context->font_desc;
348 }
349
350 /**
351  * pango_context_set_language:
352  * @context: a #PangoContext
353  * @language: the new language tag.
354  *
355  * Sets the global language tag for the context.  The default language
356  * for the locale of the running process can be found using
357  * pango_language_get_default().
358  **/
359 void
360 pango_context_set_language (PangoContext *context,
361                             PangoLanguage    *language)
362 {
363   g_return_if_fail (context != NULL);
364
365   context->set_language = language;
366   if (language)
367     context->language = language;
368   else
369     context->language = pango_language_get_default ();
370 }
371
372 /**
373  * pango_context_get_language:
374  * @context: a #PangoContext
375  *
376  * Retrieves the global language tag for the context.
377  *
378  * Return value: the global language tag.
379  **/
380 PangoLanguage *
381 pango_context_get_language (PangoContext *context)
382 {
383   g_return_val_if_fail (context != NULL, NULL);
384
385   return context->set_language;
386 }
387
388 /**
389  * pango_context_set_base_dir:
390  * @context: a #PangoContext
391  * @direction: the new base direction
392  *
393  * Sets the base direction for the context.
394  *
395  * The base direction is used in applying the Unicode bidirectional
396  * algorithm; if the @direction is %PANGO_DIRECTION_LTR or
397  * %PANGO_DIRECTION_RTL, then the value will be used as the paragraph
398  * direction in the Unicode bidirectional algorithm.  A value of
399  * %PANGO_DIRECTION_WEAK_LTR or %PANGO_DIRECTION_WEAK_RTL is used only
400  * for paragraphs that do not contain any strong characters themselves.
401  **/
402 void
403 pango_context_set_base_dir (PangoContext  *context,
404                             PangoDirection direction)
405 {
406   g_return_if_fail (context != NULL);
407
408   context->base_dir = direction;
409 }
410
411 /**
412  * pango_context_get_base_dir:
413  * @context: a #PangoContext
414  *
415  * Retrieves the base direction for the context. See
416  * pango_context_set_base_dir().
417  *
418  * Return value: the base direction for the context.
419  **/
420 PangoDirection
421 pango_context_get_base_dir (PangoContext *context)
422 {
423   g_return_val_if_fail (context != NULL, PANGO_DIRECTION_LTR);
424
425   return context->base_dir;
426 }
427
428 /**
429  * pango_context_set_base_gravity:
430  * @context: a #PangoContext
431  * @gravity: the new base gravity
432  *
433  * Sets the base gravity for the context.
434  *
435  * The base gravity is used in laying vertical text out.
436  *
437  * Since: 1.16
438  **/
439 void
440 pango_context_set_base_gravity (PangoContext  *context,
441                                 PangoGravity gravity)
442 {
443   g_return_if_fail (context != NULL);
444
445   context->base_gravity = gravity;
446
447   update_resolved_gravity (context);
448 }
449
450 /**
451  * pango_context_get_base_gravity:
452  * @context: a #PangoContext
453  *
454  * Retrieves the base gravity for the context. See
455  * pango_context_set_base_gravity().
456  *
457  * Return value: the base gravity for the context.
458  *
459  * Since: 1.16
460  **/
461 PangoGravity
462 pango_context_get_base_gravity (PangoContext *context)
463 {
464   g_return_val_if_fail (context != NULL, PANGO_GRAVITY_SOUTH);
465
466   return context->base_gravity;
467 }
468
469 /**
470  * pango_context_get_gravity:
471  * @context: a #PangoContext
472  *
473  * Retrieves the gravity for the context. This is similar to
474  * pango_context_get_base_gravity(), except for when the base gravity
475  * is %PANGO_GRAVITY_AUTO for which pango_gravity_get_for_matrix() is used
476  * to return the gravity from the current context matrix.
477  *
478  * Return value: the resolved gravity for the context.
479  *
480  * Since: 1.16
481  **/
482 PangoGravity
483 pango_context_get_gravity (PangoContext *context)
484 {
485   g_return_val_if_fail (context != NULL, PANGO_GRAVITY_SOUTH);
486
487   return context->resolved_gravity;
488 }
489
490 /**
491  * pango_context_set_gravity_hint:
492  * @context: a #PangoContext
493  * @hint: the new gravity hint
494  *
495  * Sets the gravity hint for the context.
496  *
497  * The gravity hint is used in laying vertical text out, and is only relevant
498  * if gravity of the context as returned by pango_context_get_gravity()
499  * is set %PANGO_GRAVITY_EAST or %PANGO_GRAVITY_WEST.
500  *
501  * Since: 1.16
502  **/
503 void
504 pango_context_set_gravity_hint (PangoContext    *context,
505                                 PangoGravityHint hint)
506 {
507   g_return_if_fail (context != NULL);
508
509   context->gravity_hint = hint;
510 }
511
512 /**
513  * pango_context_get_gravity_hint:
514  * @context: a #PangoContext
515  *
516  * Retrieves the gravity hint for the context. See
517  * pango_context_set_gravity_hint() for details.
518  *
519  * Return value: the gravity hint for the context.
520  *
521  * Since: 1.16
522  **/
523 PangoGravityHint
524 pango_context_get_gravity_hint (PangoContext *context)
525 {
526   g_return_val_if_fail (context != NULL, PANGO_GRAVITY_HINT_NATURAL);
527
528   return context->gravity_hint;
529 }
530
531 /**********************************************************************/
532
533 static gboolean
534 advance_attr_iterator_to (PangoAttrIterator *iterator,
535                           int                start_index)
536 {
537   int start_range, end_range;
538
539   pango_attr_iterator_range (iterator, &start_range, &end_range);
540
541   while (start_index >= end_range)
542     {
543       if (!pango_attr_iterator_next (iterator))
544         return FALSE;
545       pango_attr_iterator_range (iterator, &start_range, &end_range);
546     }
547
548   if (start_range > start_index)
549     g_warning ("In pango_itemize(), the cached iterator passed in "
550                "had already moved beyond the start_index");
551
552   return TRUE;
553 }
554
555 /***************************************************************************
556  * We cache the results of character,fontset => font,shaper in a hash table
557  ***************************************************************************/
558
559 typedef struct {
560   GHashTable *hash;
561 } ShaperFontCache;
562
563 typedef struct {
564   PangoEngineShape *shape_engine;
565   PangoFont *font;
566 } ShaperFontElement;
567
568 static void
569 shaper_font_cache_destroy (ShaperFontCache *cache)
570 {
571   g_hash_table_destroy (cache->hash);
572   g_slice_free (ShaperFontCache, cache);
573 }
574
575 static void
576 shaper_font_element_destroy (ShaperFontElement *element)
577 {
578   if (element->shape_engine)
579     g_object_unref (element->shape_engine);
580   if (element->font)
581     g_object_unref (element->font);
582   g_slice_free (ShaperFontElement, element);
583 }
584
585 static ShaperFontCache *
586 get_shaper_font_cache (PangoFontset *fontset)
587 {
588   ShaperFontCache *cache;
589
590   static GQuark cache_quark = 0;
591   if (G_UNLIKELY (!cache_quark))
592     cache_quark = g_quark_from_static_string ("pango-shaper-font-cache");
593
594   cache = g_object_get_qdata (G_OBJECT (fontset), cache_quark);
595   if (!cache)
596     {
597       cache = g_slice_new (ShaperFontCache);
598       cache->hash = g_hash_table_new_full (g_direct_hash, NULL,
599                                            NULL, (GDestroyNotify)shaper_font_element_destroy);
600
601       g_object_set_qdata_full (G_OBJECT (fontset), cache_quark,
602                                cache, (GDestroyNotify)shaper_font_cache_destroy);
603     }
604
605   return cache;
606 }
607
608 static gboolean
609 shaper_font_cache_get (ShaperFontCache   *cache,
610                        gunichar           wc,
611                        PangoEngineShape **shape_engine,
612                        PangoFont        **font)
613 {
614   ShaperFontElement *element;
615
616   element = g_hash_table_lookup (cache->hash, GUINT_TO_POINTER (wc));
617   if (element)
618     {
619       *shape_engine = element->shape_engine;
620       *font = element->font;
621
622       return TRUE;
623     }
624   else
625     return FALSE;
626 }
627
628 static void
629 shaper_font_cache_insert (ShaperFontCache   *cache,
630                           gunichar           wc,
631                           PangoEngineShape  *shape_engine,
632                           PangoFont         *font)
633 {
634   ShaperFontElement *element = g_slice_new (ShaperFontElement);
635   element->shape_engine = shape_engine ? g_object_ref (shape_engine) : NULL;
636   element->font = font ? g_object_ref (font) : NULL;
637
638   g_hash_table_insert (cache->hash, GUINT_TO_POINTER (wc), element);
639 }
640
641 /**********************************************************************/
642
643 typedef enum {
644   EMBEDDING_CHANGED    = 1 << 0,
645   SCRIPT_CHANGED       = 1 << 1,
646   LANG_CHANGED         = 1 << 2,
647   FONT_CHANGED         = 1 << 3,
648   DERIVED_LANG_CHANGED = 1 << 4,
649   WIDTH_CHANGED        = 1 << 5
650 } ChangedFlags;
651
652
653
654 typedef struct _PangoWidthIter PangoWidthIter;
655
656 struct _PangoWidthIter
657 {
658         const gchar *text_start;
659         const gchar *text_end;
660         const gchar *start;
661         const gchar *end;
662         gboolean wide;
663 };
664
665 typedef struct _ItemizeState ItemizeState;
666
667
668
669 struct _ItemizeState
670 {
671   PangoContext *context;
672   const char *text;
673   const char *end;
674
675   const char *run_start;
676   const char *run_end;
677
678   GList *result;
679   PangoItem *item;
680
681   guint8 *embedding_levels;
682   int embedding_end_offset;
683   const char *embedding_end;
684   guint8 embedding;
685
686   PangoGravity gravity;
687   PangoGravityHint gravity_hint;
688   PangoGravity resolved_gravity;
689   PangoGravity font_desc_gravity;
690   gboolean centered_baseline;
691
692   PangoAttrIterator *attr_iter;
693   gboolean free_attr_iter;
694   const char *attr_end;
695   PangoFontDescription *font_desc;
696   PangoLanguage *lang;
697   GSList *extra_attrs;
698   gboolean copy_extra_attrs;
699
700   ChangedFlags changed;
701
702   PangoScriptIter script_iter;
703   const char *script_end;
704   PangoScript script;
705
706   PangoWidthIter width_iter;
707
708   PangoLanguage *derived_lang;
709   PangoEngineLang *lang_engine;
710
711   PangoFontset *current_fonts;
712   ShaperFontCache *cache;
713   PangoFont *base_font;
714   gboolean enable_fallback;
715
716   GSList *exact_engines;
717   GSList *fallback_engines;
718 };
719
720 static void
721 update_embedding_end (ItemizeState *state)
722 {
723   state->embedding = state->embedding_levels[state->embedding_end_offset];
724   while (state->embedding_end < state->end &&
725          state->embedding_levels[state->embedding_end_offset] == state->embedding)
726     {
727       state->embedding_end_offset++;
728       state->embedding_end = g_utf8_next_char (state->embedding_end);
729     }
730
731   state->changed |= EMBEDDING_CHANGED;
732 }
733
734 static PangoAttribute *
735 find_attribute (GSList        *attr_list,
736                 PangoAttrType  type)
737 {
738   GSList *node;
739
740   for (node = attr_list; node; node = node->next)
741     if (((PangoAttribute *) node->data)->klass->type == type)
742       return (PangoAttribute *) node->data;
743
744   return NULL;
745 }
746
747 static void
748 update_attr_iterator (ItemizeState *state)
749 {
750   PangoLanguage *old_lang;
751   PangoAttribute *attr;
752   int end_index;
753
754   pango_attr_iterator_range (state->attr_iter, NULL, &end_index);
755   if (end_index < state->end - state->text)
756     state->attr_end = state->text + end_index;
757   else
758     state->attr_end = state->end;
759
760   old_lang = state->lang;
761   if (state->font_desc)
762     pango_font_description_free (state->font_desc);
763   state->font_desc = pango_font_description_copy_static (state->context->font_desc);
764   pango_attr_iterator_get_font (state->attr_iter, state->font_desc,
765                                 &state->lang, &state->extra_attrs);
766   if (pango_font_description_get_set_fields (state->font_desc) & PANGO_FONT_MASK_GRAVITY)
767     state->font_desc_gravity = pango_font_description_get_gravity (state->font_desc);
768   else
769     state->font_desc_gravity = PANGO_GRAVITY_AUTO;
770
771   state->copy_extra_attrs = FALSE;
772
773   if (!state->lang)
774     state->lang = state->context->language;
775
776   attr = find_attribute (state->extra_attrs, PANGO_ATTR_FALLBACK);
777   state->enable_fallback = (attr == NULL || ((PangoAttrInt *)attr)->value);
778
779   attr = find_attribute (state->extra_attrs, PANGO_ATTR_GRAVITY);
780   state->gravity = attr == NULL ? PANGO_GRAVITY_AUTO : ((PangoAttrInt *)attr)->value;
781
782   attr = find_attribute (state->extra_attrs, PANGO_ATTR_GRAVITY_HINT);
783   state->gravity_hint = attr == NULL ? state->context->gravity_hint : (PangoGravityHint)((PangoAttrInt *)attr)->value;
784
785   state->changed |= FONT_CHANGED;
786   if (state->lang != old_lang)
787     state->changed |= LANG_CHANGED;
788 }
789
790 static void
791 update_end (ItemizeState *state)
792 {
793   state->run_end = state->embedding_end;
794   if (state->attr_end < state->run_end)
795     state->run_end = state->attr_end;
796   if (state->script_end < state->run_end)
797     state->run_end = state->script_end;
798   if (state->width_iter.end < state->run_end)
799     state->run_end = state->width_iter.end;
800 }
801
802 static void
803 width_iter_next(PangoWidthIter* iter)
804 {
805   iter->start = iter->end;
806
807   if (iter->end < iter->text_end)
808     {
809       gunichar ch = g_utf8_get_char (iter->end);
810       iter->wide = g_unichar_iswide (ch);
811     }
812
813   while (iter->end < iter->text_end)
814     {
815       gunichar ch = g_utf8_get_char (iter->end);
816       if (g_unichar_iswide (ch) != iter->wide)
817         break;
818       iter->end = g_utf8_next_char (iter->end);
819     }
820 }
821
822 static void
823 width_iter_init (PangoWidthIter* iter, const char* text, int length)
824 {
825   iter->text_start = text;
826   iter->text_end = text + length;
827   iter->start = iter->end = text;
828
829   width_iter_next (iter);
830 }
831
832 static void
833 itemize_state_init (ItemizeState      *state,
834                     PangoContext      *context,
835                     const char        *text,
836                     PangoDirection     base_dir,
837                     int                start_index,
838                     int                length,
839                     PangoAttrList     *attrs,
840                     PangoAttrIterator *cached_iter,
841                     const PangoFontDescription *desc)
842 {
843
844   state->context = context;
845   state->text = text;
846   state->end = text + start_index + length;
847
848   state->result = NULL;
849   state->item = NULL;
850
851   state->run_start = text + start_index;
852
853   /* First, apply the bidirectional algorithm to break
854    * the text into directional runs.
855    */
856   state->embedding_levels = pango_log2vis_get_embedding_levels (text + start_index, length, &base_dir);
857
858   state->embedding_end_offset = 0;
859   state->embedding_end = text + start_index;
860   update_embedding_end (state);
861
862   /* Initialize the attribute iterator
863    */
864   if (cached_iter)
865     {
866       state->attr_iter = cached_iter;
867       state->free_attr_iter = FALSE;
868     }
869   else if (attrs)
870     {
871       state->attr_iter = pango_attr_list_get_iterator (attrs);
872       state->free_attr_iter = TRUE;
873     }
874   else
875     {
876       state->attr_iter = NULL;
877       state->free_attr_iter = FALSE;
878     }
879
880   if (state->attr_iter)
881     {
882       state->font_desc = NULL;
883       state->lang = NULL;
884
885       advance_attr_iterator_to (state->attr_iter, start_index);
886       update_attr_iterator (state);
887     }
888   else
889     {
890       state->font_desc = pango_font_description_copy_static (desc ? desc : state->context->font_desc);
891       state->lang = state->context->language;
892       state->extra_attrs = NULL;
893       state->copy_extra_attrs = FALSE;
894
895       state->attr_end = state->end;
896       state->enable_fallback = TRUE;
897     }
898
899   /* Initialize the script iterator
900    */
901   _pango_script_iter_init (&state->script_iter, text + start_index, length);
902   pango_script_iter_get_range (&state->script_iter, NULL,
903                                &state->script_end, &state->script);
904
905   /* Initialize the width iterator */
906   width_iter_init (&state->width_iter, text + start_index, length);
907
908   update_end (state);
909
910   if (pango_font_description_get_set_fields (state->font_desc) & PANGO_FONT_MASK_GRAVITY)
911     state->font_desc_gravity = pango_font_description_get_gravity (state->font_desc);
912   else
913     state->font_desc_gravity = PANGO_GRAVITY_AUTO;
914
915   state->gravity = PANGO_GRAVITY_AUTO;
916   state->centered_baseline = PANGO_GRAVITY_IS_VERTICAL (state->context->resolved_gravity);
917   state->gravity_hint = state->context->gravity_hint;
918   state->resolved_gravity = PANGO_GRAVITY_AUTO;
919   state->derived_lang = NULL;
920   state->lang_engine = NULL;
921   state->current_fonts = NULL;
922   state->cache = NULL;
923   state->exact_engines = NULL;
924   state->fallback_engines = NULL;
925   state->base_font = NULL;
926
927   state->changed = EMBEDDING_CHANGED | SCRIPT_CHANGED | LANG_CHANGED | FONT_CHANGED | WIDTH_CHANGED;
928 }
929
930 static gboolean
931 itemize_state_next (ItemizeState *state)
932 {
933   if (state->run_end == state->end)
934     return FALSE;
935
936   state->changed = 0;
937
938   state->run_start = state->run_end;
939
940   if (state->run_end == state->embedding_end)
941     {
942       update_embedding_end (state);
943     }
944
945   if (state->run_end == state->attr_end)
946     {
947       pango_attr_iterator_next (state->attr_iter);
948       update_attr_iterator (state);
949     }
950
951   if (state->run_end == state->script_end)
952     {
953       pango_script_iter_next (&state->script_iter);
954       pango_script_iter_get_range (&state->script_iter, NULL,
955                                    &state->script_end, &state->script);
956       state->changed |= SCRIPT_CHANGED;
957     }
958   if (state->run_end == state->width_iter.end)
959     {
960       width_iter_next (&state->width_iter);
961       state->changed |= WIDTH_CHANGED;
962     }
963
964   update_end (state);
965
966   return TRUE;
967 }
968
969 static GSList *
970 copy_attr_slist (GSList *attr_slist)
971 {
972   GSList *new_list = NULL;
973   GSList *l;
974
975   for (l = attr_slist; l; l = l->next)
976     new_list = g_slist_prepend (new_list, pango_attribute_copy (l->data));
977
978   return g_slist_reverse (new_list);
979 }
980
981 static void
982 itemize_state_fill_shaper (ItemizeState     *state,
983                            PangoEngineShape *shape_engine,
984                            PangoFont        *font)
985 {
986   GList *l;
987
988   for (l = state->result; l; l = l->next)
989     {
990       PangoItem *item = l->data;
991       if (item->analysis.shape_engine)
992         break;
993       if (font)
994         item->analysis.font = g_object_ref (font);
995       else
996         item->analysis.font = NULL;
997       item->analysis.shape_engine = shape_engine;
998     }
999 }
1000
1001 static void
1002 itemize_state_add_character (ItemizeState     *state,
1003                              PangoEngineShape *shape_engine,
1004                              PangoFont        *font,
1005                              gboolean          force_break,
1006                              const char       *pos)
1007 {
1008   if (state->item)
1009     {
1010       if (!state->item->analysis.shape_engine && shape_engine)
1011         {
1012           itemize_state_fill_shaper (state, shape_engine, font);
1013         }
1014       else if (state->item->analysis.shape_engine && !shape_engine)
1015         {
1016           font = state->item->analysis.font;
1017           shape_engine = state->item->analysis.shape_engine;
1018         }
1019
1020       if (!force_break &&
1021           state->item->analysis.lang_engine == state->lang_engine &&
1022           state->item->analysis.shape_engine == shape_engine &&
1023           state->item->analysis.font == font)
1024         {
1025           state->item->num_chars++;
1026           return;
1027         }
1028
1029       state->item->length = (pos - state->text) - state->item->offset;
1030     }
1031
1032   state->item = pango_item_new ();
1033   state->item->offset = pos - state->text;
1034   state->item->length = 0;
1035   state->item->num_chars = 1;
1036   state->item->analysis.shape_engine = shape_engine;
1037   state->item->analysis.lang_engine = state->lang_engine;
1038
1039   if (font)
1040     g_object_ref (font);
1041   state->item->analysis.font = font;
1042
1043   state->item->analysis.level = state->embedding;
1044   state->item->analysis.gravity = state->resolved_gravity;
1045
1046   /* The level vs. gravity dance:
1047    *    - If gravity is SOUTH, leave level untouched.
1048    *    - If gravity is NORTH, step level one up, to
1049    *      not get mirrored upside-down text.
1050    *    - If gravity is EAST, step up to an even level, as
1051    *      it's a clockwise-rotated layout, so the rotated
1052    *      top is unrotated left.
1053    *    - If gravity is WEST, step up to an odd level, as
1054    *      it's a counter-clockwise-rotated layout, so the rotated
1055    *      top is unrotated right.
1056    *
1057    * A similar dance is performed in pango-layout.c:
1058    * line_set_resolved_dir().  Keep in synch.
1059    */
1060   switch (state->item->analysis.gravity)
1061     {
1062       case PANGO_GRAVITY_SOUTH:
1063       default:
1064         break;
1065       case PANGO_GRAVITY_NORTH:
1066         state->item->analysis.level++;
1067         break;
1068       case PANGO_GRAVITY_EAST:
1069         state->item->analysis.level += 1;
1070         state->item->analysis.level &= ~1;
1071         break;
1072       case PANGO_GRAVITY_WEST:
1073         state->item->analysis.level |= 1;
1074         break;
1075     }
1076
1077   state->item->analysis.flags = state->centered_baseline ? PANGO_ANALYSIS_FLAG_CENTERED_BASELINE : 0;
1078
1079   state->item->analysis.script = state->script;
1080   state->item->analysis.language = state->derived_lang;
1081
1082   if (state->copy_extra_attrs)
1083     {
1084       state->item->analysis.extra_attrs = copy_attr_slist (state->extra_attrs);
1085     }
1086   else
1087     {
1088       state->item->analysis.extra_attrs = state->extra_attrs;
1089       state->copy_extra_attrs = TRUE;
1090     }
1091
1092   state->result = g_list_prepend (state->result, state->item);
1093 }
1094
1095 static void
1096 get_engines (PangoContext  *context,
1097              PangoLanguage *lang,
1098              PangoScript    script,
1099              GSList       **exact_engines,
1100              GSList       **fallback_engines)
1101 {
1102   const char *engine_type = pango_font_map_get_shape_engine_type (context->font_map);
1103   PangoMap *shaper_map = pango_find_map (lang,
1104                                          g_quark_from_string (PANGO_ENGINE_TYPE_SHAPE),
1105                                          g_quark_from_string (engine_type));
1106   pango_map_get_engines (shaper_map, script,
1107                          exact_engines, fallback_engines);
1108 }
1109
1110 typedef struct {
1111   PangoLanguage *lang;
1112   gunichar wc;
1113   GSList *engines;
1114   PangoEngineShape *shape_engine;
1115   PangoFont *font;
1116 } GetShaperFontInfo;
1117
1118 static gboolean
1119 get_shaper_and_font_foreach (PangoFontset *fontset G_GNUC_UNUSED,
1120                              PangoFont    *font,
1121                              gpointer      data)
1122 {
1123   GetShaperFontInfo *info = data;
1124   GSList *l;
1125
1126   if (G_UNLIKELY (!font))
1127     return FALSE;
1128
1129   for (l = info->engines; l; l = l->next)
1130     {
1131       PangoEngineShape *engine = l->data;
1132       PangoCoverageLevel level;
1133
1134       level = _pango_engine_shape_covers (engine, font,
1135                                           info->lang, info->wc);
1136       if (level != PANGO_COVERAGE_NONE)
1137         {
1138           info->shape_engine = engine;
1139           info->font = font;
1140           return TRUE;
1141         }
1142     }
1143
1144   return FALSE;
1145 }
1146
1147 static PangoFont *
1148 get_base_font (ItemizeState *state)
1149 {
1150   if (!state->base_font)
1151     state->base_font = pango_font_map_load_font (state->context->font_map,
1152                                                  state->context,
1153                                                  state->font_desc);
1154   return state->base_font;
1155 }
1156
1157 static PangoScript
1158 get_script (ItemizeState      *state)
1159 {
1160   /* Always use a basic shaper for vertical layout (ie, east/west gravity)
1161    * as none of our script shapers support vertical shaping right now.
1162    *
1163    * XXX Should move the knowledge into the shaper interface.
1164    */
1165
1166   if (PANGO_GRAVITY_IS_VERTICAL (state->resolved_gravity))
1167     return PANGO_SCRIPT_COMMON;
1168   else
1169     return state->script;
1170 }
1171
1172 static gboolean
1173 get_shaper_and_font (ItemizeState      *state,
1174                      gunichar           wc,
1175                      PangoEngineShape **shape_engine,
1176                      PangoFont        **font)
1177 {
1178   GetShaperFontInfo info;
1179
1180   /* We'd need a separate cache when fallback is disabled, but since lookup
1181    * with fallback disabled is faster anyways, we just skip caching */
1182   if (state->enable_fallback && shaper_font_cache_get (state->cache, wc, shape_engine, font))
1183     return *shape_engine != NULL;
1184
1185   if (!state->exact_engines && !state->fallback_engines)
1186     get_engines (state->context, state->derived_lang, get_script (state),
1187                  &state->exact_engines, &state->fallback_engines);
1188
1189   info.lang = state->derived_lang;
1190   info.wc = wc;
1191   info.shape_engine = NULL;
1192   info.font = NULL;
1193
1194   info.engines = state->exact_engines;
1195   if (info.engines)
1196     {
1197       if (state->enable_fallback)
1198         pango_fontset_foreach (state->current_fonts, get_shaper_and_font_foreach, &info);
1199       else
1200         get_shaper_and_font_foreach (NULL, get_base_font (state), &info);
1201
1202       if (info.shape_engine)
1203         {
1204           *shape_engine = info.shape_engine;
1205           *font = info.font;
1206
1207           /* skip caching if fallback disabled (see above) */
1208           if (state->enable_fallback)
1209             shaper_font_cache_insert (state->cache, wc, *shape_engine, *font);
1210
1211           return TRUE;
1212         }
1213     }
1214
1215   info.engines = state->fallback_engines;
1216   if (info.engines)
1217     {
1218       if (state->enable_fallback)
1219         pango_fontset_foreach (state->current_fonts, get_shaper_and_font_foreach, &info);
1220       else
1221         get_shaper_and_font_foreach (NULL, get_base_font (state), &info);
1222     }
1223
1224   *shape_engine = info.shape_engine;
1225   *font = info.font;
1226
1227   /* skip caching if fallback disabled (see above) */
1228   if (state->enable_fallback)
1229     shaper_font_cache_insert (state->cache, wc, *shape_engine, *font);
1230
1231   return *shape_engine != NULL;
1232 }
1233
1234 static void
1235 itemize_state_reset_shape_engines (ItemizeState *state)
1236 {
1237   g_slist_free (state->exact_engines);
1238   state->exact_engines = NULL;
1239   g_slist_free (state->fallback_engines);
1240   state->fallback_engines = NULL;
1241 }
1242
1243 static PangoLanguage *
1244 compute_derived_language (PangoLanguage *lang,
1245                           PangoScript    script)
1246 {
1247   PangoLanguage *derived_lang;
1248
1249   /* Make sure the language tag is consistent with the derived
1250    * script. There is no point in marking up a section of
1251    * Arabic text with the "en" language tag.
1252    */
1253   if (lang && pango_language_includes_script (lang, script))
1254     derived_lang = lang;
1255   else
1256     {
1257       derived_lang = pango_script_get_sample_language (script);
1258       /* If we don't find a sample language for the script, we
1259        * use a language tag that shouldn't actually be used
1260        * anywhere. This keeps fontconfig (for the PangoFc*
1261        * backend) from using the language tag to affect the
1262        * sort order. I don't have a reference for 'xx' being
1263        * safe here, though Keith Packard claims it is.
1264        */
1265       if (!derived_lang)
1266         derived_lang = pango_language_from_string ("xx");
1267     }
1268
1269   return derived_lang;
1270 }
1271
1272 static PangoMap *
1273 get_lang_map (PangoLanguage *lang)
1274 {
1275   static guint engine_type_id = 0;
1276   static guint render_type_id = 0;
1277
1278   if (engine_type_id == 0)
1279     {
1280       engine_type_id = g_quark_from_static_string (PANGO_ENGINE_TYPE_LANG);
1281       render_type_id = g_quark_from_static_string (PANGO_RENDER_TYPE_NONE);
1282     }
1283
1284   return pango_find_map (lang, engine_type_id, render_type_id);
1285 }
1286
1287 static void
1288 itemize_state_update_for_new_run (ItemizeState *state)
1289 {
1290   /* This block should be moved to update_attr_iterator, but I'm too lazy to
1291    * do it right now */
1292   if (state->changed & (FONT_CHANGED | SCRIPT_CHANGED | WIDTH_CHANGED))
1293     {
1294       PangoGravity old_gravity = state->resolved_gravity;
1295
1296       /* Font-desc gravity overrides everything */
1297       if (state->font_desc_gravity != PANGO_GRAVITY_AUTO)
1298         {
1299           state->resolved_gravity = state->font_desc_gravity;
1300         }
1301       else
1302         {
1303           PangoGravity gravity = state->gravity;
1304           PangoGravityHint gravity_hint = state->gravity_hint;
1305
1306           if (G_LIKELY (gravity == PANGO_GRAVITY_AUTO))
1307             gravity = state->context->resolved_gravity;
1308
1309           state->resolved_gravity = pango_gravity_get_for_script_and_width (state->script,
1310                                                                             state->width_iter.wide,
1311                                                                             gravity,
1312                                                                             gravity_hint);
1313         }
1314
1315       if (old_gravity != state->resolved_gravity)
1316         {
1317           pango_font_description_set_gravity (state->font_desc, state->resolved_gravity);
1318           state->changed |= FONT_CHANGED;
1319         }
1320     }
1321
1322   if (state->changed & (SCRIPT_CHANGED | LANG_CHANGED))
1323     {
1324       PangoLanguage *old_derived_lang = state->derived_lang;
1325       state->derived_lang = compute_derived_language (state->lang, state->script);
1326       if (old_derived_lang != state->derived_lang)
1327         state->changed |= DERIVED_LANG_CHANGED;
1328     }
1329
1330   if ((state->changed & DERIVED_LANG_CHANGED) || !state->lang_engine)
1331     {
1332       PangoMap *lang_map = get_lang_map (state->derived_lang);
1333       state->lang_engine = (PangoEngineLang *)pango_map_get_engine (lang_map, state->script);
1334     }
1335
1336   if (state->changed & (SCRIPT_CHANGED | DERIVED_LANG_CHANGED))
1337     itemize_state_reset_shape_engines (state);
1338
1339   if (state->changed & (FONT_CHANGED | DERIVED_LANG_CHANGED) &&
1340       state->current_fonts)
1341     {
1342       g_object_unref (state->current_fonts);
1343       state->current_fonts = NULL;
1344       state->cache = NULL;
1345     }
1346
1347   if (!state->current_fonts)
1348     {
1349       state->current_fonts = pango_font_map_load_fontset (state->context->font_map,
1350                                                           state->context,
1351                                                           state->font_desc,
1352                                                           state->derived_lang);
1353       state->cache = get_shaper_font_cache (state->current_fonts);
1354     }
1355
1356   if ((state->changed & FONT_CHANGED) && state->base_font)
1357     {
1358       g_object_unref (state->base_font);
1359       state->base_font = NULL;
1360     }
1361 }
1362
1363 static const char *
1364 string_from_script (PangoScript script)
1365 {
1366   static GEnumClass *class = NULL;
1367   GEnumValue *value;
1368   if (!class)
1369     class = g_type_class_ref (PANGO_TYPE_SCRIPT);
1370
1371   value = g_enum_get_value (class, script);
1372   if (!value)
1373     return string_from_script (PANGO_SCRIPT_INVALID_CODE);
1374
1375   return value->value_nick;
1376 }
1377
1378 static void
1379 itemize_state_process_run (ItemizeState *state)
1380 {
1381   const char *p;
1382   gboolean last_was_forced_break = FALSE;
1383
1384   /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 4.0;
1385    * update this if that changes. */
1386 #define LINE_SEPARATOR 0x2028
1387
1388   itemize_state_update_for_new_run (state);
1389
1390   /* We should never get an empty run */
1391   g_assert (state->run_end != state->run_start);
1392
1393   for (p = state->run_start;
1394        p < state->run_end;
1395        p = g_utf8_next_char (p))
1396     {
1397       gunichar wc = g_utf8_get_char (p);
1398       gboolean is_forced_break = (wc == '\t' || wc == LINE_SEPARATOR);
1399       PangoEngineShape *shape_engine;
1400       PangoFont *font;
1401
1402       /* We don't want space characters to affect font selection; in general,
1403        * it's always wrong to select a font just to render a space.  But until
1404        * we have a better solution, choosing a font for spaces seems to work
1405        * better.  However, all fonts are assumed to cover ASCII space, so that
1406        * one is an exception.  See bug #355987.
1407        *
1408        * The exception of PrivateUse and Unassigned characters is necessary
1409        * to be able to render any of them. (for private or being encoded
1410        * scripts, etc.) (Recent glib returns true in isprint for PrivateUse.)
1411        */
1412       if (wc == 0x0020 ||
1413           G_UNLIKELY (!g_unichar_isprint (wc) &&
1414                       g_unichar_type (wc) != G_UNICODE_PRIVATE_USE &&
1415                       g_unichar_type (wc) != G_UNICODE_UNASSIGNED))
1416         {
1417           shape_engine = NULL;
1418           font = NULL;
1419         }
1420       else
1421         {
1422           get_shaper_and_font (state, wc, &shape_engine, &font);
1423         }
1424
1425       itemize_state_add_character (state,
1426                                    shape_engine, font,
1427                                    is_forced_break || last_was_forced_break,
1428                                    p);
1429
1430       last_was_forced_break = is_forced_break;
1431     }
1432
1433   /* Finish the final item from the current segment */
1434   state->item->length = (p - state->text) - state->item->offset;
1435   if (!state->item->analysis.shape_engine)
1436     {
1437       PangoEngineShape *shape_engine;
1438       PangoFont *font;
1439
1440       if (G_UNLIKELY (!get_shaper_and_font (state, ' ', &shape_engine, &font)))
1441         {
1442           /* If no shaper was found, warn only once per fontmap/script pair */
1443           PangoFontMap *fontmap = state->context->font_map;
1444           const char *script_name = string_from_script (get_script (state));
1445
1446           if (!g_object_get_data (G_OBJECT (fontmap), script_name))
1447             {
1448               g_warning ("failed to choose a font, expect ugly output. engine-type='%s', script='%s'",
1449                          pango_font_map_get_shape_engine_type (fontmap),
1450                          script_name);
1451
1452               g_object_set_data_full (G_OBJECT (fontmap), script_name,
1453                                       GINT_TO_POINTER (1), NULL);
1454             }
1455
1456           shape_engine = _pango_get_fallback_shaper ();
1457           font = NULL;
1458         }
1459
1460       itemize_state_fill_shaper (state, shape_engine, font);
1461     }
1462   state->item = NULL;
1463 }
1464
1465 static void
1466 itemize_state_finish (ItemizeState *state)
1467 {
1468   g_free (state->embedding_levels);
1469   if (state->free_attr_iter)
1470     pango_attr_iterator_destroy (state->attr_iter);
1471   _pango_script_iter_fini (&state->script_iter);
1472   pango_font_description_free (state->font_desc);
1473
1474   itemize_state_reset_shape_engines (state);
1475   if (state->current_fonts)
1476     g_object_unref (state->current_fonts);
1477   if (state->base_font)
1478     g_object_unref (state->base_font);
1479 }
1480
1481 /**
1482  * pango_itemize_with_base_dir:
1483  * @context:   a structure holding information that affects
1484                the itemization process.
1485  * @text:      the text to itemize.
1486  * @start_index: first byte in @text to process
1487  * @length:    the number of bytes (not characters) to process
1488  *             after @start_index.
1489  *             This must be >= 0.
1490  * @base_dir:  base direction to use for bidirectional processing
1491  * @attrs:     the set of attributes that apply to @text.
1492  * @cached_iter:      Cached attribute iterator, or %NULL
1493  *
1494  * Like pango_itemize(), but the base direction to use when
1495  * computing bidirectional levels (see pango_context_set_base_dir ()),
1496  * is specified explicitly rather than gotten from the #PangoContext.
1497  *
1498  * Return value: a #GList of #PangoItem structures.  The items should be
1499  * freed using pango_item_free() probably in combination with g_list_foreach(),
1500  * and the list itself using g_list_free().
1501  *
1502  * Since: 1.4
1503  */
1504 GList *
1505 pango_itemize_with_base_dir (PangoContext      *context,
1506                              PangoDirection     base_dir,
1507                              const char        *text,
1508                              int                start_index,
1509                              int                length,
1510                              PangoAttrList     *attrs,
1511                              PangoAttrIterator *cached_iter)
1512 {
1513   ItemizeState state;
1514
1515   g_return_val_if_fail (context != NULL, NULL);
1516   g_return_val_if_fail (start_index >= 0, NULL);
1517   g_return_val_if_fail (length >= 0, NULL);
1518   g_return_val_if_fail (length == 0 || text != NULL, NULL);
1519
1520   if (length == 0)
1521     return NULL;
1522
1523   itemize_state_init (&state, context, text, base_dir, start_index, length,
1524                       attrs, cached_iter, NULL);
1525
1526   do
1527     itemize_state_process_run (&state);
1528   while (itemize_state_next (&state));
1529
1530   itemize_state_finish (&state);
1531
1532   return g_list_reverse (state.result);
1533 }
1534
1535 static GList *
1536 itemize_with_font (PangoContext               *context,
1537                    const char                 *text,
1538                    int                         start_index,
1539                    int                         length,
1540                    const PangoFontDescription *desc)
1541 {
1542   ItemizeState state;
1543
1544   if (length == 0)
1545     return NULL;
1546
1547   itemize_state_init (&state, context, text, context->base_dir, start_index, length,
1548                       NULL, NULL, desc);
1549
1550   do
1551     itemize_state_process_run (&state);
1552   while (itemize_state_next (&state));
1553
1554   itemize_state_finish (&state);
1555
1556   return g_list_reverse (state.result);
1557 }
1558
1559 /**
1560  * pango_itemize:
1561  * @context:   a structure holding information that affects
1562                the itemization process.
1563  * @text:      the text to itemize.
1564  * @start_index: first byte in @text to process
1565  * @length:    the number of bytes (not characters) to process
1566  *             after @start_index.
1567  *             This must be >= 0.
1568  * @attrs:     the set of attributes that apply to @text.
1569  * @cached_iter:      Cached attribute iterator, or %NULL
1570  *
1571  * Breaks a piece of text into segments with consistent
1572  * directional level and shaping engine. Each byte of @text will
1573  * be contained in exactly one of the items in the returned list;
1574  * the generated list of items will be in logical order (the start
1575  * offsets of the items are ascending).
1576  *
1577  * @cached_iter should be an iterator over @attrs currently positioned at a
1578  * range before or containing @start_index; @cached_iter will be advanced to
1579  * the range covering the position just after @start_index + @length.
1580  * (i.e. if itemizing in a loop, just keep passing in the same @cached_iter).
1581  *
1582  * Return value: a #GList of #PangoItem structures.
1583  */
1584 GList *
1585 pango_itemize (PangoContext      *context,
1586                const char        *text,
1587                int                start_index,
1588                int                length,
1589                PangoAttrList     *attrs,
1590                PangoAttrIterator *cached_iter)
1591 {
1592   g_return_val_if_fail (context != NULL, NULL);
1593   g_return_val_if_fail (start_index >= 0, NULL);
1594   g_return_val_if_fail (length >= 0, NULL);
1595   g_return_val_if_fail (length == 0 || text != NULL, NULL);
1596
1597   return pango_itemize_with_base_dir (context, context->base_dir,
1598                                       text, start_index, length, attrs, cached_iter);
1599 }
1600
1601 static gboolean
1602 get_first_metrics_foreach (PangoFontset  *fontset,
1603                            PangoFont     *font,
1604                            gpointer       data)
1605 {
1606   PangoFontMetrics *fontset_metrics = data;
1607   PangoLanguage *language = PANGO_FONTSET_GET_CLASS (fontset)->get_language (fontset);
1608   PangoFontMetrics *font_metrics = pango_font_get_metrics (font, language);
1609   guint save_ref_count;
1610
1611   /* Initialize the fontset metrics to metrics of the first font in the
1612    * fontset; saving the refcount and restoring it is a bit of hack but avoids
1613    * having to update this code for each metrics addition.
1614    */
1615   save_ref_count = fontset_metrics->ref_count;
1616   *fontset_metrics = *font_metrics;
1617   fontset_metrics->ref_count = save_ref_count;
1618
1619   pango_font_metrics_unref (font_metrics);
1620
1621   return TRUE;                  /* Stops iteration */
1622 }
1623
1624 static PangoFontMetrics *
1625 get_base_metrics (PangoFontset *fontset)
1626 {
1627   PangoFontMetrics *metrics = pango_font_metrics_new ();
1628
1629   /* Initialize the metrics from the first font in the fontset */
1630   pango_fontset_foreach (fontset, get_first_metrics_foreach, metrics);
1631
1632   return metrics;
1633 }
1634
1635 static void
1636 update_metrics_from_items (PangoFontMetrics *metrics,
1637                            PangoLanguage    *language,
1638                            const char       *text,
1639                            GList            *items)
1640
1641 {
1642   GHashTable *fonts_seen = g_hash_table_new (NULL, NULL);
1643   PangoGlyphString *glyphs = pango_glyph_string_new ();
1644   GList *l;
1645
1646   metrics->approximate_char_width = 0;
1647
1648   for (l = items; l; l = l->next)
1649     {
1650       PangoItem *item = l->data;
1651       PangoFont *font = item->analysis.font;
1652
1653       if (font != NULL && g_hash_table_lookup (fonts_seen, font) == NULL)
1654         {
1655           PangoFontMetrics *raw_metrics = pango_font_get_metrics (font, language);
1656           g_hash_table_insert (fonts_seen, font, font);
1657
1658           /* metrics will already be initialized from the first font in the fontset */
1659           metrics->ascent = MAX (metrics->ascent, raw_metrics->ascent);
1660           metrics->descent = MAX (metrics->descent, raw_metrics->descent);
1661           pango_font_metrics_unref (raw_metrics);
1662         }
1663
1664       pango_shape (text + item->offset, item->length, &item->analysis, glyphs);
1665       metrics->approximate_char_width += pango_glyph_string_get_width (glyphs);
1666     }
1667
1668   pango_glyph_string_free (glyphs);
1669   g_hash_table_destroy (fonts_seen);
1670
1671   metrics->approximate_char_width /= pango_utf8_strwidth (text);
1672 }
1673
1674 /**
1675  * pango_context_get_metrics:
1676  * @context: a #PangoContext
1677  * @desc: a #PangoFontDescription structure.  %NULL means that the font
1678  *            description from the context will be used.
1679  * @language: language tag used to determine which script to get the metrics
1680  *            for. %NULL means that the language tag from the context will
1681  *            be used. If no language tag is set on the context, metrics
1682  *            for the default language (as determined by
1683  *            pango_language_get_default()) will be returned.
1684  *
1685  * Get overall metric information for a particular font
1686  * description.  Since the metrics may be substantially different for
1687  * different scripts, a language tag can be provided to indicate that
1688  * the metrics should be retrieved that correspond to the script(s)
1689  * used by that language.
1690  *
1691  * The #PangoFontDescription is interpreted in the same way as
1692  * by pango_itemize(), and the family name may be a comma separated
1693  * list of figures. If characters from multiple of these families
1694  * would be used to render the string, then the returned fonts would
1695  * be a composite of the metrics for the fonts loaded for the
1696  * individual families.
1697  *
1698  * Return value: a #PangoFontMetrics object. The caller must call pango_font_metrics_unref()
1699  *   when finished using the object.
1700  **/
1701 PangoFontMetrics *
1702 pango_context_get_metrics (PangoContext                 *context,
1703                            const PangoFontDescription   *desc,
1704                            PangoLanguage                *language)
1705 {
1706   PangoFontset *current_fonts = NULL;
1707   PangoFontMetrics *metrics;
1708   const char *sample_str;
1709   GList *items;
1710
1711   g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
1712
1713   if (!desc)
1714     desc = context->font_desc;
1715
1716   if (!language)
1717     language = context->language;
1718
1719   current_fonts = pango_font_map_load_fontset (context->font_map, context, desc, language);
1720   metrics = get_base_metrics (current_fonts);
1721
1722   sample_str = pango_language_get_sample_string (language);
1723   items = itemize_with_font (context, sample_str, 0, strlen (sample_str), desc);
1724
1725   update_metrics_from_items (metrics, language, sample_str, items);
1726
1727   g_list_foreach (items, (GFunc)pango_item_free, NULL);
1728   g_list_free (items);
1729
1730   g_object_unref (current_fonts);
1731
1732   return metrics;
1733 }