1 /* Pango
2  * pangocairo-font.c: Cairo font handling
3  *
4  * Copyright (C) 2000-2005 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 
24 #include <math.h>
25 #include <string.h>
26 
27 #include "pangocairo.h"
28 #include "pangocairo-private.h"
29 #include "pango-font-private.h"
30 #include "pango-impl-utils.h"
31 
32 #define PANGO_CAIRO_FONT_PRIVATE(font)		\
33   ((PangoCairoFontPrivate *)			\
34    (font == NULL ? NULL :			\
35     G_STRUCT_MEMBER_P (font,			\
36     PANGO_CAIRO_FONT_GET_IFACE(PANGO_CAIRO_FONT(font))->cf_priv_offset)))
37 
38 typedef PangoCairoFontIface PangoCairoFontInterface;
G_DEFINE_INTERFACE(PangoCairoFont,pango_cairo_font,PANGO_TYPE_FONT)39 G_DEFINE_INTERFACE (PangoCairoFont, pango_cairo_font, PANGO_TYPE_FONT)
40 
41 static void
42 pango_cairo_font_default_init (PangoCairoFontIface *iface)
43 {
44 }
45 
46 
47 static PangoCairoFontPrivateScaledFontData *
_pango_cairo_font_private_scaled_font_data_create(void)48 _pango_cairo_font_private_scaled_font_data_create (void)
49 {
50   return g_slice_new (PangoCairoFontPrivateScaledFontData);
51 }
52 
53 static void
_pango_cairo_font_private_scaled_font_data_destroy(PangoCairoFontPrivateScaledFontData * data)54 _pango_cairo_font_private_scaled_font_data_destroy (PangoCairoFontPrivateScaledFontData *data)
55 {
56   if (data)
57     {
58       cairo_font_options_destroy (data->options);
59       g_slice_free (PangoCairoFontPrivateScaledFontData, data);
60     }
61 }
62 
63 cairo_scaled_font_t *
_pango_cairo_font_private_get_scaled_font(PangoCairoFontPrivate * cf_priv)64 _pango_cairo_font_private_get_scaled_font (PangoCairoFontPrivate *cf_priv)
65 {
66   cairo_font_face_t *font_face;
67 
68   if (G_LIKELY (cf_priv->scaled_font))
69     return cf_priv->scaled_font;
70 
71   /* need to create it */
72 
73   if (G_UNLIKELY (cf_priv->data == NULL))
74     {
75       /* we have tried to create and failed before */
76       return NULL;
77     }
78 
79   font_face = (* PANGO_CAIRO_FONT_GET_IFACE (cf_priv->cfont)->create_font_face) (cf_priv->cfont);
80   if (G_UNLIKELY (font_face == NULL))
81     goto done;
82 
83   cf_priv->scaled_font = cairo_scaled_font_create (font_face,
84 						   &cf_priv->data->font_matrix,
85 						   &cf_priv->data->ctm,
86 						   cf_priv->data->options);
87 
88   cairo_font_face_destroy (font_face);
89 
90 done:
91 
92   if (G_UNLIKELY (cf_priv->scaled_font == NULL || cairo_scaled_font_status (cf_priv->scaled_font) != CAIRO_STATUS_SUCCESS))
93     {
94       cairo_scaled_font_t *scaled_font = cf_priv->scaled_font;
95       PangoFont *font = PANGO_FONT (cf_priv->cfont);
96       static GQuark warned_quark = 0; /* MT-safe */
97       if (!warned_quark)
98 	warned_quark = g_quark_from_static_string ("pangocairo-scaledfont-warned");
99 
100       if (!g_object_get_qdata (G_OBJECT (font), warned_quark))
101 	{
102 	  PangoFontDescription *desc;
103 	  char *s;
104 
105 	  desc = pango_font_describe (font);
106 	  s = pango_font_description_to_string (desc);
107 	  pango_font_description_free (desc);
108 
109 	  g_warning ("failed to create cairo %s, expect ugly output. the offending font is '%s'",
110 		     font_face ? "scaled font" : "font face",
111 		     s);
112 
113 	  if (!font_face)
114 		g_warning ("font_face is NULL");
115 	  else
116 		g_warning ("font_face status is: %s",
117 			   cairo_status_to_string (cairo_font_face_status (font_face)));
118 
119 	  if (!scaled_font)
120 		g_warning ("scaled_font is NULL");
121 	  else
122 		g_warning ("scaled_font status is: %s",
123 			   cairo_status_to_string (cairo_scaled_font_status (scaled_font)));
124 
125 	  g_free (s);
126 
127 	  g_object_set_qdata_full (G_OBJECT (font), warned_quark,
128 				   GINT_TO_POINTER (1), NULL);
129 	}
130     }
131 
132   _pango_cairo_font_private_scaled_font_data_destroy (cf_priv->data);
133   cf_priv->data = NULL;
134 
135   return cf_priv->scaled_font;
136 }
137 
138 /**
139  * pango_cairo_font_get_scaled_font:
140  * @font: (nullable): a `PangoFont` from a `PangoCairoFontMap`
141  *
142  * Gets the `cairo_scaled_font_t` used by @font.
143  * The scaled font can be referenced and kept using
144  * cairo_scaled_font_reference().
145  *
146  * Return value: (transfer none) (nullable): the `cairo_scaled_font_t`
147  *   used by @font
148  *
149  * Since: 1.18
150  */
151 cairo_scaled_font_t *
pango_cairo_font_get_scaled_font(PangoCairoFont * cfont)152 pango_cairo_font_get_scaled_font (PangoCairoFont *cfont)
153 {
154   PangoCairoFontPrivate *cf_priv;
155 
156   if (G_UNLIKELY (!cfont))
157     return NULL;
158 
159   cf_priv = PANGO_CAIRO_FONT_PRIVATE (cfont);
160 
161   return _pango_cairo_font_private_get_scaled_font (cf_priv);
162 }
163 
164 /**
165  * _pango_cairo_font_install:
166  * @font: a `PangoCairoFont`
167  * @cr: a #cairo_t
168  *
169  * Makes @font the current font for rendering in the specified
170  * Cairo context.
171  *
172  * Return value: %TRUE if font was installed successfully, %FALSE otherwise.
173  */
174 gboolean
_pango_cairo_font_install(PangoFont * font,cairo_t * cr)175 _pango_cairo_font_install (PangoFont *font,
176 			   cairo_t   *cr)
177 {
178   cairo_scaled_font_t *scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
179 
180   if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
181     return FALSE;
182 
183   cairo_set_scaled_font (cr, scaled_font);
184 
185   return TRUE;
186 }
187 
188 
189 static int
max_glyph_width(PangoLayout * layout)190 max_glyph_width (PangoLayout *layout)
191 {
192   int max_width = 0;
193   GSList *l, *r;
194 
195   for (l = pango_layout_get_lines_readonly (layout); l; l = l->next)
196     {
197       PangoLayoutLine *line = l->data;
198 
199       for (r = line->runs; r; r = r->next)
200 	{
201 	  PangoGlyphString *glyphs = ((PangoGlyphItem *)r->data)->glyphs;
202 	  int i;
203 
204 	  for (i = 0; i < glyphs->num_glyphs; i++)
205 	    if (glyphs->glyphs[i].geometry.width > max_width)
206 	      max_width = glyphs->glyphs[i].geometry.width;
207 	}
208     }
209 
210   return max_width;
211 }
212 
213 typedef struct _PangoCairoFontMetricsInfo
214 {
215   const char       *sample_str;
216   PangoFontMetrics *metrics;
217 } PangoCairoFontMetricsInfo;
218 
219 PangoFontMetrics *
_pango_cairo_font_get_metrics(PangoFont * font,PangoLanguage * language)220 _pango_cairo_font_get_metrics (PangoFont     *font,
221 			       PangoLanguage *language)
222 {
223   PangoCairoFont *cfont = (PangoCairoFont *) font;
224   PangoCairoFontPrivate *cf_priv = PANGO_CAIRO_FONT_PRIVATE (font);
225   PangoCairoFontMetricsInfo *info = NULL; /* Quiet gcc */
226   GSList *tmp_list;
227   static int in_get_metrics;
228 
229   const char *sample_str = pango_language_get_sample_string (language);
230 
231   tmp_list = cf_priv->metrics_by_lang;
232   while (tmp_list)
233     {
234       info = tmp_list->data;
235 
236       if (info->sample_str == sample_str)    /* We _don't_ need strcmp */
237 	break;
238 
239       tmp_list = tmp_list->next;
240     }
241 
242   if (!tmp_list)
243     {
244       PangoFontMap *fontmap;
245       PangoContext *context;
246       cairo_font_options_t *font_options;
247       PangoLayout *layout;
248       PangoRectangle extents;
249       PangoFontDescription *desc;
250       cairo_scaled_font_t *scaled_font;
251       cairo_matrix_t cairo_matrix;
252       PangoMatrix pango_matrix;
253       PangoMatrix identity = PANGO_MATRIX_INIT;
254       glong sample_str_width;
255 
256       int height, shift;
257 
258       /* XXX this is racy.  need a ref'ing getter... */
259       fontmap = pango_font_get_font_map (font);
260       if (!fontmap)
261         return pango_font_metrics_new ();
262       fontmap = g_object_ref (fontmap);
263 
264       info = g_slice_new0 (PangoCairoFontMetricsInfo);
265 
266       cf_priv->metrics_by_lang = g_slist_prepend (cf_priv->metrics_by_lang, info);
267 
268       info->sample_str = sample_str;
269 
270       scaled_font = _pango_cairo_font_private_get_scaled_font (cf_priv);
271 
272       context = pango_font_map_create_context (fontmap);
273       pango_context_set_language (context, language);
274 
275       font_options = cairo_font_options_create ();
276       cairo_scaled_font_get_font_options (scaled_font, font_options);
277       pango_cairo_context_set_font_options (context, font_options);
278       cairo_font_options_destroy (font_options);
279 
280       info->metrics = (* PANGO_CAIRO_FONT_GET_IFACE (font)->create_base_metrics_for_context) (cfont, context);
281 
282       /* We now need to adjust the base metrics for ctm */
283       cairo_scaled_font_get_ctm (scaled_font, &cairo_matrix);
284       pango_matrix.xx = cairo_matrix.xx;
285       pango_matrix.yx = cairo_matrix.yx;
286       pango_matrix.xy = cairo_matrix.xy;
287       pango_matrix.yy = cairo_matrix.yy;
288       pango_matrix.x0 = 0;
289       pango_matrix.y0 = 0;
290       if (G_UNLIKELY (0 != memcmp (&identity, &pango_matrix, 4 * sizeof (double))))
291         {
292 	  double xscale = pango_matrix_get_font_scale_factor (&pango_matrix);
293 	  if (xscale) xscale = 1 / xscale;
294 
295 	  info->metrics->ascent *= xscale;
296 	  info->metrics->descent *= xscale;
297 	  info->metrics->height *= xscale;
298 	  info->metrics->underline_position *= xscale;
299 	  info->metrics->underline_thickness *= xscale;
300 	  info->metrics->strikethrough_position *= xscale;
301 	  info->metrics->strikethrough_thickness *= xscale;
302 	}
303 
304       /* Set the matrix on the context so we don't have to adjust the derived
305        * metrics. */
306       pango_context_set_matrix (context, &pango_matrix);
307 
308       /* Ugly. We need to prevent recursion when we call into
309        * PangoLayout to determine approximate char width.
310        */
311       if (!in_get_metrics)
312         {
313           in_get_metrics = 1;
314 
315           /* Update approximate_*_width now */
316           layout = pango_layout_new (context);
317           desc = pango_font_describe_with_absolute_size (font);
318           pango_layout_set_font_description (layout, desc);
319           pango_font_description_free (desc);
320 
321           pango_layout_set_text (layout, sample_str, -1);
322           pango_layout_get_extents (layout, NULL, &extents);
323 
324           sample_str_width = pango_utf8_strwidth (sample_str);
325           g_assert (sample_str_width > 0);
326           info->metrics->approximate_char_width = extents.width / sample_str_width;
327 
328           pango_layout_set_text (layout, "0123456789", -1);
329           info->metrics->approximate_digit_width = max_glyph_width (layout);
330 
331           g_object_unref (layout);
332           in_get_metrics = 0;
333         }
334 
335       /* We may actually reuse ascent/descent we got from cairo here.  that's
336        * in cf_priv->font_extents.
337        */
338       height = info->metrics->ascent + info->metrics->descent;
339       switch (cf_priv->gravity)
340 	{
341 	  default:
342 	  case PANGO_GRAVITY_AUTO:
343 	  case PANGO_GRAVITY_SOUTH:
344 	    break;
345 	  case PANGO_GRAVITY_NORTH:
346 	    info->metrics->ascent = info->metrics->descent;
347 	    break;
348 	  case PANGO_GRAVITY_EAST:
349 	  case PANGO_GRAVITY_WEST:
350 	    {
351 	      int ascent = height / 2;
352 	      if (cf_priv->is_hinted)
353 	        ascent = PANGO_UNITS_ROUND (ascent);
354 	      info->metrics->ascent = ascent;
355 	    }
356 	}
357       shift = (height - info->metrics->ascent) - info->metrics->descent;
358       info->metrics->descent += shift;
359       info->metrics->underline_position -= shift;
360       info->metrics->strikethrough_position -= shift;
361       info->metrics->ascent = height - info->metrics->descent;
362 
363       g_object_unref (context);
364       g_object_unref (fontmap);
365     }
366 
367   return pango_font_metrics_ref (info->metrics);
368 }
369 
370 static PangoCairoFontHexBoxInfo *
_pango_cairo_font_private_get_hex_box_info(PangoCairoFontPrivate * cf_priv)371 _pango_cairo_font_private_get_hex_box_info (PangoCairoFontPrivate *cf_priv)
372 {
373   const char hexdigits[] = "0123456789ABCDEF";
374   char c[2] = {0, 0};
375   PangoFont *mini_font;
376   PangoCairoFontHexBoxInfo *hbi;
377 
378   /* for metrics hinting */
379   double scale_x = 1., scale_x_inv = 1., scale_y = 1., scale_y_inv = 1.;
380   gboolean is_hinted;
381 
382   int i;
383   int rows;
384   double pad;
385   double width = 0;
386   double height = 0;
387   cairo_font_options_t *font_options;
388   cairo_font_extents_t font_extents;
389   double size, mini_size;
390   PangoFontDescription *desc;
391   cairo_scaled_font_t *scaled_font, *scaled_mini_font;
392   PangoMatrix pango_ctm, pango_font_matrix;
393   cairo_matrix_t cairo_ctm, cairo_font_matrix;
394   /*PangoGravity gravity;*/
395 
396   if (!cf_priv)
397     return NULL;
398 
399   if (cf_priv->hbi)
400     return cf_priv->hbi;
401 
402   scaled_font = _pango_cairo_font_private_get_scaled_font (cf_priv);
403   if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
404     return NULL;
405 
406   is_hinted = cf_priv->is_hinted;
407 
408   font_options = cairo_font_options_create ();
409   desc = pango_font_describe_with_absolute_size ((PangoFont *)cf_priv->cfont);
410   /*gravity = pango_font_description_get_gravity (desc);*/
411 
412   cairo_scaled_font_get_ctm (scaled_font, &cairo_ctm);
413   cairo_scaled_font_get_font_matrix (scaled_font, &cairo_font_matrix);
414   cairo_scaled_font_get_font_options (scaled_font, font_options);
415   /* I started adding support for vertical hexboxes here, but it's too much
416    * work.  Easier to do with cairo user fonts and vertical writing mode
417    * support in cairo.
418    */
419   /*cairo_matrix_rotate (&cairo_ctm, pango_gravity_to_rotation (gravity));*/
420   pango_ctm.xx = cairo_ctm.xx;
421   pango_ctm.yx = cairo_ctm.yx;
422   pango_ctm.xy = cairo_ctm.xy;
423   pango_ctm.yy = cairo_ctm.yy;
424   pango_ctm.x0 = cairo_ctm.x0;
425   pango_ctm.y0 = cairo_ctm.y0;
426   pango_font_matrix.xx = cairo_font_matrix.xx;
427   pango_font_matrix.yx = cairo_font_matrix.yx;
428   pango_font_matrix.xy = cairo_font_matrix.xy;
429   pango_font_matrix.yy = cairo_font_matrix.yy;
430   pango_font_matrix.x0 = cairo_font_matrix.x0;
431   pango_font_matrix.y0 = cairo_font_matrix.y0;
432 
433   size = pango_matrix_get_font_scale_factor (&pango_font_matrix) /
434 	 pango_matrix_get_font_scale_factor (&pango_ctm);
435 
436   if (is_hinted)
437     {
438       /* prepare for some hinting */
439       double x, y;
440 
441       x = 1.; y = 0.;
442       cairo_matrix_transform_distance (&cairo_ctm, &x, &y);
443       scale_x = sqrt (x*x + y*y);
444       scale_x_inv = 1 / scale_x;
445 
446       x = 0.; y = 1.;
447       cairo_matrix_transform_distance (&cairo_ctm, &x, &y);
448       scale_y = sqrt (x*x + y*y);
449       scale_y_inv = 1 / scale_y;
450     }
451 
452 /* we hint to the nearest device units */
453 #define HINT(value, scale, scale_inv) (ceil ((value-1e-5) * scale) * scale_inv)
454 #define HINT_X(value) HINT ((value), scale_x, scale_x_inv)
455 #define HINT_Y(value) HINT ((value), scale_y, scale_y_inv)
456 
457   /* create mini_font description */
458   {
459     PangoFontMap *fontmap;
460     PangoContext *context;
461 
462     /* XXX this is racy.  need a ref'ing getter... */
463     fontmap = pango_font_get_font_map ((PangoFont *)cf_priv->cfont);
464     if (!fontmap)
465       return NULL;
466     fontmap = g_object_ref (fontmap);
467 
468     /* we inherit most font properties for the mini font.  just
469      * change family and size.  means, you get bold hex digits
470      * in the hexbox for a bold font.
471      */
472 
473     /* We should rotate the box, not glyphs */
474     pango_font_description_unset_fields (desc, PANGO_FONT_MASK_GRAVITY);
475 
476     pango_font_description_set_family_static (desc, "monospace");
477 
478     rows = 2;
479     mini_size = size / 2.2;
480     if (is_hinted)
481       {
482 	mini_size = HINT_Y (mini_size);
483 
484 	if (mini_size < 6.0)
485 	  {
486 	    rows = 1;
487 	    mini_size = MIN (MAX (size - 1, 0), 6.0);
488 	  }
489       }
490 
491     pango_font_description_set_absolute_size (desc, pango_units_from_double (mini_size));
492 
493     /* load mini_font */
494 
495     context = pango_font_map_create_context (fontmap);
496 
497     pango_context_set_matrix (context, &pango_ctm);
498     pango_context_set_language (context, pango_script_get_sample_language (PANGO_SCRIPT_LATIN));
499     pango_cairo_context_set_font_options (context, font_options);
500     mini_font = pango_font_map_load_font (fontmap, context, desc);
501 
502     g_object_unref (context);
503     g_object_unref (fontmap);
504   }
505 
506   pango_font_description_free (desc);
507   cairo_font_options_destroy (font_options);
508 
509 
510   scaled_mini_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *) mini_font);
511   if (G_UNLIKELY (scaled_mini_font == NULL || cairo_scaled_font_status (scaled_mini_font) != CAIRO_STATUS_SUCCESS))
512     return NULL;
513 
514   for (i = 0 ; i < 16 ; i++)
515     {
516       cairo_text_extents_t extents;
517 
518       c[0] = hexdigits[i];
519       cairo_scaled_font_text_extents (scaled_mini_font, c, &extents);
520       width = MAX (width, extents.width);
521       height = MAX (height, extents.height);
522     }
523 
524   cairo_scaled_font_extents (scaled_font, &font_extents);
525   if (font_extents.ascent + font_extents.descent <= 0)
526     {
527       font_extents.ascent = PANGO_UNKNOWN_GLYPH_HEIGHT;
528       font_extents.descent = 0;
529     }
530 
531   pad = (font_extents.ascent + font_extents.descent) / 43;
532   pad = MIN (pad, mini_size);
533 
534   hbi = g_slice_new (PangoCairoFontHexBoxInfo);
535   hbi->font = (PangoCairoFont *) mini_font;
536   hbi->rows = rows;
537 
538   hbi->digit_width  = width;
539   hbi->digit_height = height;
540 
541   hbi->pad_x = pad;
542   hbi->pad_y = pad;
543 
544   if (is_hinted)
545     {
546       hbi->digit_width  = HINT_X (hbi->digit_width);
547       hbi->digit_height = HINT_Y (hbi->digit_height);
548       hbi->pad_x = HINT_X (hbi->pad_x);
549       hbi->pad_y = HINT_Y (hbi->pad_y);
550     }
551 
552   hbi->line_width = MIN (hbi->pad_x, hbi->pad_y);
553 
554   hbi->box_height = 3 * hbi->pad_y + rows * (hbi->pad_y + hbi->digit_height);
555 
556   if (rows == 1 || hbi->box_height <= font_extents.ascent)
557     {
558       hbi->box_descent = 2 * hbi->pad_y;
559     }
560   else if (hbi->box_height <= font_extents.ascent + font_extents.descent - 2 * hbi->pad_y)
561     {
562       hbi->box_descent = 2 * hbi->pad_y + hbi->box_height - font_extents.ascent;
563     }
564   else
565     {
566       hbi->box_descent = font_extents.descent * hbi->box_height /
567 			 (font_extents.ascent + font_extents.descent);
568     }
569   if (is_hinted)
570     {
571        hbi->box_descent = HINT_Y (hbi->box_descent);
572     }
573 
574   cf_priv->hbi = hbi;
575   return hbi;
576 }
577 
578 static void
_pango_cairo_font_hex_box_info_destroy(PangoCairoFontHexBoxInfo * hbi)579 _pango_cairo_font_hex_box_info_destroy (PangoCairoFontHexBoxInfo *hbi)
580 {
581   if (hbi)
582     {
583       g_object_unref (hbi->font);
584       g_slice_free (PangoCairoFontHexBoxInfo, hbi);
585     }
586 }
587 
588 PangoCairoFontHexBoxInfo *
_pango_cairo_font_get_hex_box_info(PangoCairoFont * cfont)589 _pango_cairo_font_get_hex_box_info (PangoCairoFont *cfont)
590 {
591   PangoCairoFontPrivate *cf_priv = PANGO_CAIRO_FONT_PRIVATE (cfont);
592 
593   return _pango_cairo_font_private_get_hex_box_info (cf_priv);
594 }
595 
596 void
_pango_cairo_font_private_initialize(PangoCairoFontPrivate * cf_priv,PangoCairoFont * cfont,PangoGravity gravity,const cairo_font_options_t * font_options,const PangoMatrix * pango_ctm,const cairo_matrix_t * font_matrix)597 _pango_cairo_font_private_initialize (PangoCairoFontPrivate      *cf_priv,
598 				      PangoCairoFont             *cfont,
599 				      PangoGravity                gravity,
600 				      const cairo_font_options_t *font_options,
601 				      const PangoMatrix          *pango_ctm,
602 				      const cairo_matrix_t       *font_matrix)
603 {
604   cairo_matrix_t gravity_matrix;
605 
606   cf_priv->cfont = cfont;
607   cf_priv->gravity = gravity;
608 
609   cf_priv->data = _pango_cairo_font_private_scaled_font_data_create ();
610 
611   /* first apply gravity rotation, then font_matrix, such that
612    * vertical italic text comes out "correct".  we don't do anything
613    * like baseline adjustment etc though.  should be specially
614    * handled when we support italic correction. */
615   cairo_matrix_init_rotate(&gravity_matrix,
616 			   pango_gravity_to_rotation (cf_priv->gravity));
617   cairo_matrix_multiply (&cf_priv->data->font_matrix,
618 			 font_matrix,
619 			 &gravity_matrix);
620 
621   if (pango_ctm)
622     cairo_matrix_init (&cf_priv->data->ctm,
623 		       pango_ctm->xx,
624 		       pango_ctm->yx,
625 		       pango_ctm->xy,
626 		       pango_ctm->yy,
627 		       0., 0.);
628   else
629     cairo_matrix_init_identity (&cf_priv->data->ctm);
630 
631   cf_priv->data->options = cairo_font_options_copy (font_options);
632   cf_priv->is_hinted = cairo_font_options_get_hint_metrics (font_options) != CAIRO_HINT_METRICS_OFF;
633 
634   cf_priv->scaled_font = NULL;
635   cf_priv->hbi = NULL;
636   cf_priv->glyph_extents_cache = NULL;
637   cf_priv->metrics_by_lang = NULL;
638 }
639 
640 static void
free_metrics_info(PangoCairoFontMetricsInfo * info)641 free_metrics_info (PangoCairoFontMetricsInfo *info)
642 {
643   pango_font_metrics_unref (info->metrics);
644   g_slice_free (PangoCairoFontMetricsInfo, info);
645 }
646 
647 void
_pango_cairo_font_private_finalize(PangoCairoFontPrivate * cf_priv)648 _pango_cairo_font_private_finalize (PangoCairoFontPrivate *cf_priv)
649 {
650   _pango_cairo_font_private_scaled_font_data_destroy (cf_priv->data);
651 
652   if (cf_priv->scaled_font)
653     cairo_scaled_font_destroy (cf_priv->scaled_font);
654   cf_priv->scaled_font = NULL;
655 
656   _pango_cairo_font_hex_box_info_destroy (cf_priv->hbi);
657   cf_priv->hbi = NULL;
658 
659   if (cf_priv->glyph_extents_cache)
660     g_free (cf_priv->glyph_extents_cache);
661   cf_priv->glyph_extents_cache = NULL;
662 
663   g_slist_foreach (cf_priv->metrics_by_lang, (GFunc)free_metrics_info, NULL);
664   g_slist_free (cf_priv->metrics_by_lang);
665   cf_priv->metrics_by_lang = NULL;
666 }
667 
668 gboolean
_pango_cairo_font_private_is_metrics_hinted(PangoCairoFontPrivate * cf_priv)669 _pango_cairo_font_private_is_metrics_hinted (PangoCairoFontPrivate *cf_priv)
670 {
671   return cf_priv->is_hinted;
672 }
673 
674 static void
get_space_extents(PangoCairoFontPrivate * cf_priv,PangoRectangle * ink_rect,PangoRectangle * logical_rect)675 get_space_extents (PangoCairoFontPrivate *cf_priv,
676                    PangoRectangle        *ink_rect,
677                    PangoRectangle        *logical_rect)
678 {
679   const char hexdigits[] = "0123456789ABCDEF";
680   char c[2] = {0, 0};
681   int i;
682   double hex_width;
683   int width;
684 
685   /* we don't render missing spaces as hex boxes,
686    * so come up with some width to use. For lack
687    * of anything better, use average hex digit width.
688    */
689 
690   hex_width = 0;
691   for (i = 0 ; i < 16 ; i++)
692     {
693       cairo_text_extents_t extents;
694 
695       c[0] = hexdigits[i];
696       cairo_scaled_font_text_extents (cf_priv->scaled_font, c, &extents);
697       hex_width += extents.width;
698     }
699   width = pango_units_from_double (hex_width / 16);
700 
701   if (ink_rect)
702     {
703       ink_rect->x = ink_rect->y = ink_rect->height = 0;
704       ink_rect->width = width;
705     }
706   if (logical_rect)
707     {
708       *logical_rect = cf_priv->font_extents;
709       logical_rect->width = width;
710     }
711 }
712 
713 static void
_pango_cairo_font_private_get_glyph_extents_missing(PangoCairoFontPrivate * cf_priv,PangoGlyph glyph,PangoRectangle * ink_rect,PangoRectangle * logical_rect)714 _pango_cairo_font_private_get_glyph_extents_missing (PangoCairoFontPrivate *cf_priv,
715 						     PangoGlyph             glyph,
716 						     PangoRectangle        *ink_rect,
717 						     PangoRectangle        *logical_rect)
718 {
719   PangoCairoFontHexBoxInfo *hbi;
720   gunichar ch;
721   gint rows, cols;
722 
723   ch = glyph & ~PANGO_GLYPH_UNKNOWN_FLAG;
724 
725   if (ch == 0x20 || ch == 0x2423)
726     {
727       get_space_extents (cf_priv, ink_rect, logical_rect);
728       return;
729     }
730 
731   hbi = _pango_cairo_font_private_get_hex_box_info (cf_priv);
732   if (!hbi)
733     {
734       pango_font_get_glyph_extents (NULL, glyph, ink_rect, logical_rect);
735       return;
736     }
737 
738   if (G_UNLIKELY (glyph == PANGO_GLYPH_INVALID_INPUT || ch > 0x10FFFF))
739     {
740       rows = hbi->rows;
741       cols = 1;
742     }
743   else if (pango_get_ignorable_size (ch, &rows, &cols))
744     {
745       /* We special-case ignorables when rendering hex boxes */
746     }
747   else
748     {
749       rows = hbi->rows;
750       cols = ((glyph & ~PANGO_GLYPH_UNKNOWN_FLAG) > 0xffff ? 6 : 4) / rows;
751     }
752 
753   if (ink_rect)
754     {
755       ink_rect->x = PANGO_SCALE * hbi->pad_x;
756       ink_rect->y = PANGO_SCALE * (hbi->box_descent - hbi->box_height);
757       ink_rect->width = PANGO_SCALE * (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
758       ink_rect->height = PANGO_SCALE * hbi->box_height;
759     }
760 
761   if (logical_rect)
762     {
763       logical_rect->x = 0;
764       logical_rect->y = PANGO_SCALE * (hbi->box_descent - (hbi->box_height + hbi->pad_y));
765       logical_rect->width = PANGO_SCALE * (5 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
766       logical_rect->height = PANGO_SCALE * (hbi->box_height + 2 * hbi->pad_y);
767     }
768 }
769 
770 #define GLYPH_CACHE_NUM_ENTRIES 256 /* should be power of two */
771 #define GLYPH_CACHE_MASK (GLYPH_CACHE_NUM_ENTRIES - 1)
772 /* An entry in the fixed-size cache for the glyph->extents mapping.
773  * The cache is indexed by the lower N bits of the glyph (see
774  * GLYPH_CACHE_NUM_ENTRIES).  For scripts with few glyphs,
775  * this should provide pretty much instant lookups.
776  */
777 struct _PangoCairoFontGlyphExtentsCacheEntry
778 {
779   PangoGlyph     glyph;
780   int            width;
781   PangoRectangle ink_rect;
782 };
783 
784 static gboolean
_pango_cairo_font_private_glyph_extents_cache_init(PangoCairoFontPrivate * cf_priv)785 _pango_cairo_font_private_glyph_extents_cache_init (PangoCairoFontPrivate *cf_priv)
786 {
787   cairo_scaled_font_t *scaled_font = _pango_cairo_font_private_get_scaled_font (cf_priv);
788   cairo_font_extents_t font_extents;
789 
790   if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
791     return FALSE;
792 
793   cairo_scaled_font_extents (scaled_font, &font_extents);
794 
795   cf_priv->font_extents.x = 0;
796   cf_priv->font_extents.width = 0;
797   cf_priv->font_extents.height = pango_units_from_double (font_extents.ascent + font_extents.descent);
798   switch (cf_priv->gravity)
799     {
800       default:
801       case PANGO_GRAVITY_AUTO:
802       case PANGO_GRAVITY_SOUTH:
803 	cf_priv->font_extents.y = - pango_units_from_double (font_extents.ascent);
804 	break;
805       case PANGO_GRAVITY_NORTH:
806 	cf_priv->font_extents.y = - pango_units_from_double (font_extents.descent);
807 	break;
808       case PANGO_GRAVITY_EAST:
809       case PANGO_GRAVITY_WEST:
810 	{
811 	  int ascent = pango_units_from_double (font_extents.ascent + font_extents.descent) / 2;
812 	  if (cf_priv->is_hinted)
813 	    ascent = PANGO_UNITS_ROUND (ascent);
814 	  cf_priv->font_extents.y = - ascent;
815 	}
816     }
817 
818   cf_priv->glyph_extents_cache = g_new0 (PangoCairoFontGlyphExtentsCacheEntry, GLYPH_CACHE_NUM_ENTRIES);
819   /* Make sure all cache entries are invalid initially */
820   cf_priv->glyph_extents_cache[0].glyph = 1; /* glyph 1 cannot happen in bucket 0 */
821 
822   return TRUE;
823 }
824 
825 
826 /* Fills in the glyph extents cache entry
827  */
828 static void
compute_glyph_extents(PangoCairoFontPrivate * cf_priv,PangoGlyph glyph,PangoCairoFontGlyphExtentsCacheEntry * entry)829 compute_glyph_extents (PangoCairoFontPrivate  *cf_priv,
830 		       PangoGlyph              glyph,
831 		       PangoCairoFontGlyphExtentsCacheEntry *entry)
832 {
833   cairo_text_extents_t extents;
834   cairo_glyph_t cairo_glyph;
835 
836   cairo_glyph.index = glyph;
837   cairo_glyph.x = 0;
838   cairo_glyph.y = 0;
839 
840   cairo_scaled_font_glyph_extents (_pango_cairo_font_private_get_scaled_font (cf_priv),
841 				   &cairo_glyph, 1, &extents);
842 
843   entry->glyph = glyph;
844   entry->width = pango_units_from_double (extents.x_advance);
845   entry->ink_rect.x = pango_units_from_double (extents.x_bearing);
846   entry->ink_rect.y = pango_units_from_double (extents.y_bearing);
847   entry->ink_rect.width = pango_units_from_double (extents.width);
848   entry->ink_rect.height = pango_units_from_double (extents.height);
849 }
850 
851 static PangoCairoFontGlyphExtentsCacheEntry *
_pango_cairo_font_private_get_glyph_extents_cache_entry(PangoCairoFontPrivate * cf_priv,PangoGlyph glyph)852 _pango_cairo_font_private_get_glyph_extents_cache_entry (PangoCairoFontPrivate  *cf_priv,
853 							 PangoGlyph              glyph)
854 {
855   PangoCairoFontGlyphExtentsCacheEntry *entry;
856   guint idx;
857 
858   idx = glyph & GLYPH_CACHE_MASK;
859   entry = cf_priv->glyph_extents_cache + idx;
860 
861   if (entry->glyph != glyph)
862     {
863       compute_glyph_extents (cf_priv, glyph, entry);
864     }
865 
866   return entry;
867 }
868 
869 void
_pango_cairo_font_private_get_glyph_extents(PangoCairoFontPrivate * cf_priv,PangoGlyph glyph,PangoRectangle * ink_rect,PangoRectangle * logical_rect)870 _pango_cairo_font_private_get_glyph_extents (PangoCairoFontPrivate *cf_priv,
871 					     PangoGlyph             glyph,
872 					     PangoRectangle        *ink_rect,
873 					     PangoRectangle        *logical_rect)
874 {
875   PangoCairoFontGlyphExtentsCacheEntry *entry;
876 
877   if (!cf_priv ||
878       (cf_priv->glyph_extents_cache == NULL &&
879        !_pango_cairo_font_private_glyph_extents_cache_init (cf_priv)))
880     {
881       /* Get generic unknown-glyph extents. */
882       pango_font_get_glyph_extents (NULL, glyph, ink_rect, logical_rect);
883       return;
884     }
885 
886   if (glyph == PANGO_GLYPH_EMPTY)
887     {
888       if (ink_rect)
889 	ink_rect->x = ink_rect->y = ink_rect->width = ink_rect->height = 0;
890       if (logical_rect)
891 	*logical_rect = cf_priv->font_extents;
892       return;
893     }
894   else if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
895     {
896       _pango_cairo_font_private_get_glyph_extents_missing(cf_priv, glyph, ink_rect, logical_rect);
897       return;
898     }
899 
900   entry = _pango_cairo_font_private_get_glyph_extents_cache_entry (cf_priv, glyph);
901 
902   if (ink_rect)
903     *ink_rect = entry->ink_rect;
904   if (logical_rect)
905     {
906       *logical_rect = cf_priv->font_extents;
907       logical_rect->width = entry->width;
908     }
909 }
910