1 /* Pango
2  * pangofc-shape.c: Basic shaper for FreeType-based backends
3  *
4  * Copyright (C) 2000, 2007, 2009 Red Hat Software
5  * Authors:
6  *   Owen Taylor <otaylor@redhat.com>
7  *   Behdad Esfahbod <behdad@behdad.org>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24 
25 #include "config.h"
26 #include <string.h>
27 #include <math.h>
28 
29 #include "pangohb-private.h"
30 #include "pango-impl-utils.h"
31 
32 #include <glib.h>
33 
34 /* cache a single hb_buffer_t */
35 static hb_buffer_t *cached_buffer = NULL; /* MT-safe */
36 G_LOCK_DEFINE_STATIC (cached_buffer);
37 
38 static hb_buffer_t *
acquire_buffer(gboolean * free_buffer)39 acquire_buffer (gboolean *free_buffer)
40 {
41   hb_buffer_t *buffer;
42 
43   if (G_LIKELY (G_TRYLOCK (cached_buffer)))
44     {
45       if (G_UNLIKELY (!cached_buffer))
46 	cached_buffer = hb_buffer_create ();
47 
48       buffer = cached_buffer;
49       *free_buffer = FALSE;
50     }
51   else
52     {
53       buffer = hb_buffer_create ();
54       *free_buffer = TRUE;
55     }
56 
57   return buffer;
58 }
59 
60 static void
release_buffer(hb_buffer_t * buffer,gboolean free_buffer)61 release_buffer (hb_buffer_t *buffer, gboolean free_buffer)
62 {
63   if (G_LIKELY (!free_buffer))
64     {
65       hb_buffer_reset (buffer);
66       G_UNLOCK (cached_buffer);
67     }
68   else
69     hb_buffer_destroy (buffer);
70 }
71 
72 static void
apply_extra_attributes(GSList * attrs,hb_feature_t * features,guint length,guint * num_features)73 apply_extra_attributes (GSList       *attrs,
74                         hb_feature_t *features,
75                         guint         length,
76                         guint        *num_features)
77 {
78   GSList *l;
79 
80   for (l = attrs; l && *num_features < length; l = l->next)
81     {
82       PangoAttribute *attr = l->data;
83       if (attr->klass->type == PANGO_ATTR_FONT_FEATURES)
84         {
85           PangoAttrFontFeatures *fattr = (PangoAttrFontFeatures *) attr;
86           const gchar *feat;
87           const gchar *end;
88           int len;
89 
90           feat = fattr->features;
91 
92           while (feat != NULL && *num_features < length)
93             {
94               end = strchr (feat, ',');
95               if (end)
96                 len = end - feat;
97               else
98                 len = -1;
99               if (hb_feature_from_string (feat, len, &features[*num_features]))
100                 {
101                   features[*num_features].start = attr->start_index;
102                   features[*num_features].end = attr->end_index;
103                   (*num_features)++;
104                 }
105 
106               if (end == NULL)
107                 break;
108 
109               feat = end + 1;
110             }
111         }
112     }
113 
114   /* Turn off ligatures when letterspacing */
115   for (l = attrs; l && *num_features < length; l = l->next)
116     {
117       PangoAttribute *attr = l->data;
118       if (attr->klass->type == PANGO_ATTR_LETTER_SPACING)
119         {
120           hb_tag_t tags[] = {
121             HB_TAG('l','i','g','a'),
122             HB_TAG('c','l','i','g'),
123             HB_TAG('d','l','i','g'),
124           };
125           int i;
126           for (i = 0; i < G_N_ELEMENTS (tags); i++)
127             {
128               features[*num_features].tag = tags[i];
129               features[*num_features].value = 0;
130               features[*num_features].start = attr->start_index;
131               features[*num_features].end = attr->end_index;
132               (*num_features)++;
133             }
134         }
135     }
136 }
137 
138 typedef struct
139 {
140   PangoFont *font;
141   hb_font_t *parent;
142   PangoShowFlags show_flags;
143 } PangoHbShapeContext;
144 
145 static hb_bool_t
pango_hb_font_get_nominal_glyph(hb_font_t * font,void * font_data,hb_codepoint_t unicode,hb_codepoint_t * glyph,void * user_data G_GNUC_UNUSED)146 pango_hb_font_get_nominal_glyph (hb_font_t      *font,
147                                  void           *font_data,
148                                  hb_codepoint_t  unicode,
149                                  hb_codepoint_t *glyph,
150                                  void           *user_data G_GNUC_UNUSED)
151 {
152   PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
153 
154   if (context->show_flags != 0)
155     {
156       if ((context->show_flags & PANGO_SHOW_SPACES) != 0 &&
157           g_unichar_type (unicode) == G_UNICODE_SPACE_SEPARATOR)
158         {
159           /* Replace 0x20 by visible space, since we
160            * don't draw a hex box for 0x20
161            */
162           if (unicode == 0x20)
163             *glyph = PANGO_GET_UNKNOWN_GLYPH (0x2423);
164           else
165             *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
166           return TRUE;
167         }
168 
169       if ((context->show_flags & PANGO_SHOW_IGNORABLES) != 0 &&
170           pango_get_ignorable (unicode))
171         {
172           *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
173           return TRUE;
174         }
175 
176       if ((context->show_flags & PANGO_SHOW_LINE_BREAKS) != 0 &&
177           unicode == 0x2028)
178         {
179           /* Always mark LS as unknown. If it ends up
180            * at the line end, PangoLayout takes care of
181            * hiding them, and if they end up in the middle
182            * of a line, we are in single paragraph mode
183            * and want to show the LS
184            */
185           *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
186           return TRUE;
187         }
188     }
189 
190   if (hb_font_get_nominal_glyph (context->parent, unicode, glyph))
191     return TRUE;
192 
193   *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
194 
195   /* We draw our own invalid-Unicode shape, so prevent HarfBuzz
196    * from using REPLACEMENT CHARACTER. */
197   if (unicode > 0x10FFFF)
198     return TRUE;
199 
200   return FALSE;
201 }
202 
203 static hb_position_t
pango_hb_font_get_glyph_h_advance(hb_font_t * font,void * font_data,hb_codepoint_t glyph,void * user_data G_GNUC_UNUSED)204 pango_hb_font_get_glyph_h_advance (hb_font_t      *font,
205                                    void           *font_data,
206                                    hb_codepoint_t  glyph,
207                                    void           *user_data G_GNUC_UNUSED)
208 {
209   PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
210 
211   if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
212     {
213       PangoRectangle logical;
214 
215       pango_font_get_glyph_extents (context->font, glyph, NULL, &logical);
216       return logical.width;
217     }
218 
219   return hb_font_get_glyph_h_advance (context->parent, glyph);
220 }
221 
222 static hb_position_t
pango_hb_font_get_glyph_v_advance(hb_font_t * font,void * font_data,hb_codepoint_t glyph,void * user_data G_GNUC_UNUSED)223 pango_hb_font_get_glyph_v_advance (hb_font_t      *font,
224                                    void           *font_data,
225                                    hb_codepoint_t  glyph,
226                                    void           *user_data G_GNUC_UNUSED)
227 {
228   PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
229 
230   if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
231     {
232       PangoRectangle logical;
233 
234       pango_font_get_glyph_extents (context->font, glyph, NULL, &logical);
235       return logical.height;
236     }
237 
238   return hb_font_get_glyph_v_advance (context->parent, glyph);
239 }
240 
241 static hb_bool_t
pango_hb_font_get_glyph_extents(hb_font_t * font,void * font_data,hb_codepoint_t glyph,hb_glyph_extents_t * extents,void * user_data G_GNUC_UNUSED)242 pango_hb_font_get_glyph_extents (hb_font_t          *font,
243                                  void               *font_data,
244                                  hb_codepoint_t      glyph,
245                                  hb_glyph_extents_t *extents,
246                                  void               *user_data G_GNUC_UNUSED)
247 {
248   PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;
249 
250   if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
251     {
252       PangoRectangle ink;
253 
254       pango_font_get_glyph_extents (context->font, glyph, &ink, NULL);
255 
256       extents->x_bearing = ink.x;
257       extents->y_bearing = ink.y;
258       extents->width     = ink.width;
259       extents->height    = ink.height;
260 
261       return TRUE;
262     }
263 
264   return hb_font_get_glyph_extents (context->parent, glyph, extents);
265 }
266 
267 static hb_font_t *
pango_font_get_hb_font_for_context(PangoFont * font,PangoHbShapeContext * context)268 pango_font_get_hb_font_for_context (PangoFont           *font,
269                                     PangoHbShapeContext *context)
270 {
271   hb_font_t *hb_font;
272   static hb_font_funcs_t *funcs;
273 
274   hb_font = pango_font_get_hb_font (font);
275 
276   if (G_UNLIKELY (!funcs))
277     {
278       funcs = hb_font_funcs_create ();
279 
280       hb_font_funcs_set_nominal_glyph_func (funcs, pango_hb_font_get_nominal_glyph, NULL, NULL);
281       hb_font_funcs_set_glyph_h_advance_func (funcs, pango_hb_font_get_glyph_h_advance, NULL, NULL);
282       hb_font_funcs_set_glyph_v_advance_func (funcs, pango_hb_font_get_glyph_v_advance, NULL, NULL);
283       hb_font_funcs_set_glyph_extents_func (funcs, pango_hb_font_get_glyph_extents, NULL, NULL);
284 
285       hb_font_funcs_make_immutable (funcs);
286     }
287 
288   context->font = font;
289   context->parent = hb_font;
290 
291   hb_font = hb_font_create_sub_font (hb_font);
292   hb_font_set_funcs (hb_font, funcs, context, NULL);
293 
294   return hb_font;
295 }
296 
297 static PangoShowFlags
find_show_flags(const PangoAnalysis * analysis)298 find_show_flags (const PangoAnalysis *analysis)
299 {
300   GSList *l;
301   PangoShowFlags flags = 0;
302 
303   for (l = analysis->extra_attrs; l; l = l->next)
304     {
305       PangoAttribute *attr = l->data;
306 
307       if (attr->klass->type == PANGO_ATTR_SHOW)
308         flags |= ((PangoAttrInt*)attr)->value;
309     }
310 
311   return flags;
312 }
313 
314 void
pango_hb_shape(PangoFont * font,const char * item_text,unsigned int item_length,const PangoAnalysis * analysis,PangoGlyphString * glyphs,const char * paragraph_text,unsigned int paragraph_length)315 pango_hb_shape (PangoFont           *font,
316                 const char          *item_text,
317                 unsigned int         item_length,
318                 const PangoAnalysis *analysis,
319                 PangoGlyphString    *glyphs,
320                 const char          *paragraph_text,
321                 unsigned int         paragraph_length)
322 {
323   PangoHbShapeContext context = { 0, };
324   hb_buffer_flags_t hb_buffer_flags;
325   hb_font_t *hb_font;
326   hb_buffer_t *hb_buffer;
327   hb_direction_t hb_direction;
328   gboolean free_buffer;
329   hb_glyph_info_t *hb_glyph;
330   hb_glyph_position_t *hb_position;
331   int last_cluster;
332   guint i, num_glyphs;
333   unsigned int item_offset = item_text - paragraph_text;
334   hb_feature_t features[32];
335   unsigned int num_features = 0;
336   PangoGlyphInfo *infos;
337 
338   g_return_if_fail (font != NULL);
339   g_return_if_fail (analysis != NULL);
340 
341   context.show_flags = find_show_flags (analysis);
342   hb_font = pango_font_get_hb_font_for_context (font, &context);
343   hb_buffer = acquire_buffer (&free_buffer);
344 
345   hb_direction = PANGO_GRAVITY_IS_VERTICAL (analysis->gravity) ? HB_DIRECTION_TTB : HB_DIRECTION_LTR;
346   if (analysis->level % 2)
347     hb_direction = HB_DIRECTION_REVERSE (hb_direction);
348   if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
349     hb_direction = HB_DIRECTION_REVERSE (hb_direction);
350 
351   hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT;
352 
353   if (context.show_flags & PANGO_SHOW_IGNORABLES)
354     hb_buffer_flags |= HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES;
355 
356   /* setup buffer */
357 
358   hb_buffer_set_direction (hb_buffer, hb_direction);
359   hb_buffer_set_script (hb_buffer, (hb_script_t) g_unicode_script_to_iso15924 (analysis->script));
360   hb_buffer_set_language (hb_buffer, hb_language_from_string (pango_language_to_string (analysis->language), -1));
361   hb_buffer_set_cluster_level (hb_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
362   hb_buffer_set_flags (hb_buffer, hb_buffer_flags);
363   hb_buffer_set_invisible_glyph (hb_buffer, PANGO_GLYPH_EMPTY);
364 
365   hb_buffer_add_utf8 (hb_buffer, paragraph_text, paragraph_length, item_offset, item_length);
366   if (analysis->flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
367     {
368       /* Insert either a Unicode or ASCII hyphen. We may
369        * want to look for script-specific hyphens here.
370        */
371       const char *p = paragraph_text + item_offset + item_length;
372       int last_char_len = p - g_utf8_prev_char (p);
373       hb_codepoint_t glyph;
374 
375       if (hb_font_get_nominal_glyph (hb_font, 0x2010, &glyph))
376         hb_buffer_add (hb_buffer, 0x2010, item_offset + item_length - last_char_len);
377       else if (hb_font_get_nominal_glyph (hb_font, '-', &glyph))
378         hb_buffer_add (hb_buffer, '-', item_offset + item_length - last_char_len);
379     }
380 
381   pango_font_get_features (font, features, G_N_ELEMENTS (features), &num_features);
382   apply_extra_attributes (analysis->extra_attrs, features, G_N_ELEMENTS (features), &num_features);
383 
384   hb_shape (hb_font, hb_buffer, features, num_features);
385 
386   if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
387     hb_buffer_reverse (hb_buffer);
388 
389   /* buffer output */
390   num_glyphs = hb_buffer_get_length (hb_buffer);
391   hb_glyph = hb_buffer_get_glyph_infos (hb_buffer, NULL);
392   pango_glyph_string_set_size (glyphs, num_glyphs);
393   infos = glyphs->glyphs;
394   last_cluster = -1;
395   for (i = 0; i < num_glyphs; i++)
396     {
397       infos[i].glyph = hb_glyph->codepoint;
398       glyphs->log_clusters[i] = hb_glyph->cluster - item_offset;
399       infos[i].attr.is_cluster_start = glyphs->log_clusters[i] != last_cluster;
400       hb_glyph++;
401       last_cluster = glyphs->log_clusters[i];
402     }
403 
404   hb_position = hb_buffer_get_glyph_positions (hb_buffer, NULL);
405   if (PANGO_GRAVITY_IS_VERTICAL (analysis->gravity))
406     for (i = 0; i < num_glyphs; i++)
407       {
408         /* 90 degrees rotation counter-clockwise. */
409 	infos[i].geometry.width    = - hb_position->y_advance;
410 	infos[i].geometry.x_offset = - hb_position->y_offset;
411 	infos[i].geometry.y_offset = - hb_position->x_offset;
412 	hb_position++;
413       }
414   else /* horizontal */
415     for (i = 0; i < num_glyphs; i++)
416       {
417 	infos[i].geometry.width    =   hb_position->x_advance;
418 	infos[i].geometry.x_offset =   hb_position->x_offset;
419 	infos[i].geometry.y_offset = - hb_position->y_offset;
420 	hb_position++;
421       }
422 
423   release_buffer (hb_buffer, free_buffer);
424   hb_font_destroy (hb_font);
425 }
426