1 /*
2  * Copyright © 2012  Google, Inc.
3  *
4  *  This is part of HarfBuzz, a text shaping library.
5  *
6  * Permission is hereby granted, without written agreement and without
7  * license or royalty fees, to use, copy, modify, and distribute this
8  * software and its documentation for any purpose, provided that the
9  * above copyright notice and the following two paragraphs appear in
10  * all copies of this software.
11  *
12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16  * DAMAGE.
17  *
18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23  *
24  * Google Author(s): Behdad Esfahbod
25  */
26 
27 #include "hb.hh"
28 #include "hb-shape-plan.hh"
29 #include "hb-shaper.hh"
30 #include "hb-font.hh"
31 #include "hb-buffer.hh"
32 
33 
34 /**
35  * SECTION:hb-shape-plan
36  * @title: hb-shape-plan
37  * @short_description: Object representing a shaping plan
38  * @include: hb.h
39  *
40  * Shape plans are an internal mechanism. Each plan contains state
41  * describing how HarfBuzz will shape a particular text segment, based on
42  * the combination of segment properties and the capabilities in the
43  * font face in use.
44  *
45  * Shape plans are not used for shaping directly, but can be queried to
46  * access certain information about how shaping will perform, given a set
47  * of specific input parameters (script, language, direction, features,
48  * etc.).
49  *
50  * Most client programs will not need to deal with shape plans directly.
51  **/
52 
53 
54 /*
55  * hb_shape_plan_key_t
56  */
57 
58 bool
init(bool copy,hb_face_t * face,const hb_segment_properties_t * props,const hb_feature_t * user_features,unsigned int num_user_features,const int * coords,unsigned int num_coords,const char * const * shaper_list)59 hb_shape_plan_key_t::init (bool                           copy,
60                            hb_face_t                     *face,
61                            const hb_segment_properties_t *props,
62                            const hb_feature_t            *user_features,
63                            unsigned int                   num_user_features,
64                            const int                     *coords,
65                            unsigned int                   num_coords,
66                            const char * const            *shaper_list)
67 {
68   hb_feature_t *features = nullptr;
69   if (copy && num_user_features && !(features = (hb_feature_t *) calloc (num_user_features, sizeof (hb_feature_t))))
70     goto bail;
71 
72   this->props = *props;
73   this->num_user_features = num_user_features;
74   this->user_features = copy ? features : user_features;
75   if (copy && num_user_features)
76   {
77     memcpy (features, user_features, num_user_features * sizeof (hb_feature_t));
78     /* Make start/end uniform to easier catch bugs. */
79     for (unsigned int i = 0; i < num_user_features; i++)
80     {
81       if (features[0].start != HB_FEATURE_GLOBAL_START)
82         features[0].start = 1;
83       if (features[0].end   != HB_FEATURE_GLOBAL_END)
84         features[0].end   = 2;
85     }
86   }
87   this->shaper_func = nullptr;
88   this->shaper_name = nullptr;
89 #ifndef HB_NO_OT_SHAPE
90   this->ot.init (face, coords, num_coords);
91 #endif
92 
93   /*
94    * Choose shaper.
95    */
96 
97 #define HB_SHAPER_PLAN(shaper) \
98         HB_STMT_START { \
99           if (face->data.shaper) \
100           { \
101             this->shaper_func = _hb_##shaper##_shape; \
102             this->shaper_name = #shaper; \
103             return true; \
104           } \
105         } HB_STMT_END
106 
107   if (unlikely (shaper_list))
108   {
109     for (; *shaper_list; shaper_list++)
110       if (false)
111         ;
112 #define HB_SHAPER_IMPLEMENT(shaper) \
113       else if (0 == strcmp (*shaper_list, #shaper)) \
114         HB_SHAPER_PLAN (shaper);
115 #include "hb-shaper-list.hh"
116 #undef HB_SHAPER_IMPLEMENT
117   }
118   else
119   {
120     const hb_shaper_entry_t *shapers = _hb_shapers_get ();
121     for (unsigned int i = 0; i < HB_SHAPERS_COUNT; i++)
122       if (false)
123         ;
124 #define HB_SHAPER_IMPLEMENT(shaper) \
125       else if (shapers[i].func == _hb_##shaper##_shape) \
126         HB_SHAPER_PLAN (shaper);
127 #include "hb-shaper-list.hh"
128 #undef HB_SHAPER_IMPLEMENT
129   }
130 #undef HB_SHAPER_PLAN
131 
132 bail:
133   ::free (features);
134   return false;
135 }
136 
137 bool
user_features_match(const hb_shape_plan_key_t * other)138 hb_shape_plan_key_t::user_features_match (const hb_shape_plan_key_t *other)
139 {
140   if (this->num_user_features != other->num_user_features)
141     return false;
142   for (unsigned int i = 0; i < num_user_features; i++)
143   {
144     if (this->user_features[i].tag   != other->user_features[i].tag   ||
145         this->user_features[i].value != other->user_features[i].value ||
146         (this->user_features[i].start == HB_FEATURE_GLOBAL_START &&
147          this->user_features[i].end   == HB_FEATURE_GLOBAL_END) !=
148         (other->user_features[i].start == HB_FEATURE_GLOBAL_START &&
149          other->user_features[i].end   == HB_FEATURE_GLOBAL_END))
150       return false;
151   }
152   return true;
153 }
154 
155 bool
equal(const hb_shape_plan_key_t * other)156 hb_shape_plan_key_t::equal (const hb_shape_plan_key_t *other)
157 {
158   return hb_segment_properties_equal (&this->props, &other->props) &&
159          this->user_features_match (other) &&
160 #ifndef HB_NO_OT_SHAPE
161          this->ot.equal (&other->ot) &&
162 #endif
163          this->shaper_func == other->shaper_func;
164 }
165 
166 
167 /*
168  * hb_shape_plan_t
169  */
170 
171 
172 /**
173  * hb_shape_plan_create: (Xconstructor)
174  * @face: #hb_face_t to use
175  * @props: The #hb_segment_properties_t of the segment
176  * @user_features: (array length=num_user_features): The list of user-selected features
177  * @num_user_features: The number of user-selected features
178  * @shaper_list: (array zero-terminated=1): List of shapers to try
179  *
180  * Constructs a shaping plan for a combination of @face, @user_features, @props,
181  * and @shaper_list.
182  *
183  * Return value: (transfer full): The shaping plan
184  *
185  * Since: 0.9.7
186  **/
187 hb_shape_plan_t *
hb_shape_plan_create(hb_face_t * face,const hb_segment_properties_t * props,const hb_feature_t * user_features,unsigned int num_user_features,const char * const * shaper_list)188 hb_shape_plan_create (hb_face_t                     *face,
189                       const hb_segment_properties_t *props,
190                       const hb_feature_t            *user_features,
191                       unsigned int                   num_user_features,
192                       const char * const            *shaper_list)
193 {
194   return hb_shape_plan_create2 (face, props,
195                                 user_features, num_user_features,
196                                 nullptr, 0,
197                                 shaper_list);
198 }
199 
200 /**
201  * hb_shape_plan_create2: (Xconstructor)
202  * @face: #hb_face_t to use
203  * @props: The #hb_segment_properties_t of the segment
204  * @user_features: (array length=num_user_features): The list of user-selected features
205  * @num_user_features: The number of user-selected features
206  * @coords: (array length=num_coords): The list of variation-space coordinates
207  * @num_coords: The number of variation-space coordinates
208  * @shaper_list: (array zero-terminated=1): List of shapers to try
209  *
210  * The variable-font version of #hb_shape_plan_create.
211  * Constructs a shaping plan for a combination of @face, @user_features, @props,
212  * and @shaper_list, plus the variation-space coordinates @coords.
213  *
214  * Return value: (transfer full): The shaping plan
215  *
216  * Since: 1.4.0
217  **/
218 hb_shape_plan_t *
hb_shape_plan_create2(hb_face_t * face,const hb_segment_properties_t * props,const hb_feature_t * user_features,unsigned int num_user_features,const int * coords,unsigned int num_coords,const char * const * shaper_list)219 hb_shape_plan_create2 (hb_face_t                     *face,
220                        const hb_segment_properties_t *props,
221                        const hb_feature_t            *user_features,
222                        unsigned int                   num_user_features,
223                        const int                     *coords,
224                        unsigned int                   num_coords,
225                        const char * const            *shaper_list)
226 {
227   DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr,
228                   "face=%p num_features=%d num_coords=%d shaper_list=%p",
229                   face,
230                   num_user_features,
231                   num_coords,
232                   shaper_list);
233 
234   assert (props->direction != HB_DIRECTION_INVALID);
235 
236   hb_shape_plan_t *shape_plan;
237 
238   if (unlikely (!props))
239     goto bail;
240   if (!(shape_plan = hb_object_create<hb_shape_plan_t> ()))
241     goto bail;
242 
243   if (unlikely (!face))
244     face = hb_face_get_empty ();
245   hb_face_make_immutable (face);
246   shape_plan->face_unsafe = face;
247 
248   if (unlikely (!shape_plan->key.init (true,
249                                        face,
250                                        props,
251                                        user_features,
252                                        num_user_features,
253                                        coords,
254                                        num_coords,
255                                        shaper_list)))
256     goto bail2;
257 #ifndef HB_NO_OT_SHAPE
258   if (unlikely (!shape_plan->ot.init0 (face, &shape_plan->key)))
259     goto bail3;
260 #endif
261 
262   return shape_plan;
263 
264 #ifndef HB_NO_OT_SHAPE
265 bail3:
266 #endif
267   shape_plan->key.free ();
268 bail2:
269   free (shape_plan);
270 bail:
271   return hb_shape_plan_get_empty ();
272 }
273 
274 /**
275  * hb_shape_plan_get_empty:
276  *
277  * Fetches the singleton empty shaping plan.
278  *
279  * Return value: (transfer full): The empty shaping plan
280  *
281  * Since: 0.9.7
282  **/
283 hb_shape_plan_t *
hb_shape_plan_get_empty()284 hb_shape_plan_get_empty ()
285 {
286   return const_cast<hb_shape_plan_t *> (&Null (hb_shape_plan_t));
287 }
288 
289 /**
290  * hb_shape_plan_reference: (skip)
291  * @shape_plan: A shaping plan
292  *
293  * Increases the reference count on the given shaping plan.
294  *
295  * Return value: (transfer full): @shape_plan
296  *
297  * Since: 0.9.7
298  **/
299 hb_shape_plan_t *
hb_shape_plan_reference(hb_shape_plan_t * shape_plan)300 hb_shape_plan_reference (hb_shape_plan_t *shape_plan)
301 {
302   return hb_object_reference (shape_plan);
303 }
304 
305 /**
306  * hb_shape_plan_destroy: (skip)
307  * @shape_plan: A shaping plan
308  *
309  * Decreases the reference count on the given shaping plan. When the
310  * reference count reaches zero, the shaping plan is destroyed,
311  * freeing all memory.
312  *
313  * Since: 0.9.7
314  **/
315 void
hb_shape_plan_destroy(hb_shape_plan_t * shape_plan)316 hb_shape_plan_destroy (hb_shape_plan_t *shape_plan)
317 {
318   if (!hb_object_destroy (shape_plan)) return;
319 
320 #ifndef HB_NO_OT_SHAPE
321   shape_plan->ot.fini ();
322 #endif
323   shape_plan->key.free ();
324   free (shape_plan);
325 }
326 
327 /**
328  * hb_shape_plan_set_user_data: (skip)
329  * @shape_plan: A shaping plan
330  * @key: The user-data key to set
331  * @data: A pointer to the user data
332  * @destroy: (nullable): A callback to call when @data is not needed anymore
333  * @replace: Whether to replace an existing data with the same key
334  *
335  * Attaches a user-data key/data pair to the given shaping plan.
336  *
337  * Return value: %true if success, %false otherwise.
338  *
339  * Since: 0.9.7
340  **/
341 hb_bool_t
hb_shape_plan_set_user_data(hb_shape_plan_t * shape_plan,hb_user_data_key_t * key,void * data,hb_destroy_func_t destroy,hb_bool_t replace)342 hb_shape_plan_set_user_data (hb_shape_plan_t    *shape_plan,
343                              hb_user_data_key_t *key,
344                              void *              data,
345                              hb_destroy_func_t   destroy,
346                              hb_bool_t           replace)
347 {
348   return hb_object_set_user_data (shape_plan, key, data, destroy, replace);
349 }
350 
351 /**
352  * hb_shape_plan_get_user_data: (skip)
353  * @shape_plan: A shaping plan
354  * @key: The user-data key to query
355  *
356  * Fetches the user data associated with the specified key,
357  * attached to the specified shaping plan.
358  *
359  * Return value: (transfer none): A pointer to the user data
360  *
361  * Since: 0.9.7
362  **/
363 void *
hb_shape_plan_get_user_data(hb_shape_plan_t * shape_plan,hb_user_data_key_t * key)364 hb_shape_plan_get_user_data (hb_shape_plan_t    *shape_plan,
365                              hb_user_data_key_t *key)
366 {
367   return hb_object_get_user_data (shape_plan, key);
368 }
369 
370 /**
371  * hb_shape_plan_get_shaper:
372  * @shape_plan: A shaping plan
373  *
374  * Fetches the shaper from a given shaping plan.
375  *
376  * Return value: (transfer none): The shaper
377  *
378  * Since: 0.9.7
379  **/
380 const char *
hb_shape_plan_get_shaper(hb_shape_plan_t * shape_plan)381 hb_shape_plan_get_shaper (hb_shape_plan_t *shape_plan)
382 {
383   return shape_plan->key.shaper_name;
384 }
385 
386 
387 static bool
_hb_shape_plan_execute_internal(hb_shape_plan_t * shape_plan,hb_font_t * font,hb_buffer_t * buffer,const hb_feature_t * features,unsigned int num_features)388 _hb_shape_plan_execute_internal (hb_shape_plan_t    *shape_plan,
389                                  hb_font_t          *font,
390                                  hb_buffer_t        *buffer,
391                                  const hb_feature_t *features,
392                                  unsigned int        num_features)
393 {
394   DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan,
395                   "num_features=%d shaper_func=%p, shaper_name=%s",
396                   num_features,
397                   shape_plan->key.shaper_func,
398                   shape_plan->key.shaper_name);
399 
400   if (unlikely (!buffer->len))
401     return true;
402 
403   assert (!hb_object_is_immutable (buffer));
404 
405   buffer->assert_unicode ();
406 
407   if (unlikely (hb_object_is_inert (shape_plan)))
408     return false;
409 
410   assert (shape_plan->face_unsafe == font->face);
411   assert (hb_segment_properties_equal (&shape_plan->key.props, &buffer->props));
412 
413 #define HB_SHAPER_EXECUTE(shaper) \
414         HB_STMT_START { \
415           return font->data.shaper && \
416                  _hb_##shaper##_shape (shape_plan, font, buffer, features, num_features); \
417         } HB_STMT_END
418 
419   if (false)
420     ;
421 #define HB_SHAPER_IMPLEMENT(shaper) \
422   else if (shape_plan->key.shaper_func == _hb_##shaper##_shape) \
423     HB_SHAPER_EXECUTE (shaper);
424 #include "hb-shaper-list.hh"
425 #undef HB_SHAPER_IMPLEMENT
426 
427 #undef HB_SHAPER_EXECUTE
428 
429   return false;
430 }
431 /**
432  * hb_shape_plan_execute:
433  * @shape_plan: A shaping plan
434  * @font: The #hb_font_t to use
435  * @buffer: The #hb_buffer_t to work upon
436  * @features: (array length=num_features): Features to enable
437  * @num_features: The number of features to enable
438  *
439  * Executes the given shaping plan on the specified buffer, using
440  * the given @font and @features.
441  *
442  * Return value: %true if success, %false otherwise.
443  *
444  * Since: 0.9.7
445  **/
446 hb_bool_t
hb_shape_plan_execute(hb_shape_plan_t * shape_plan,hb_font_t * font,hb_buffer_t * buffer,const hb_feature_t * features,unsigned int num_features)447 hb_shape_plan_execute (hb_shape_plan_t    *shape_plan,
448                        hb_font_t          *font,
449                        hb_buffer_t        *buffer,
450                        const hb_feature_t *features,
451                        unsigned int        num_features)
452 {
453   bool ret = _hb_shape_plan_execute_internal (shape_plan, font, buffer,
454                                               features, num_features);
455 
456   if (ret && buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE)
457     buffer->content_type = HB_BUFFER_CONTENT_TYPE_GLYPHS;
458 
459   return ret;
460 }
461 
462 
463 /*
464  * Caching
465  */
466 
467 /**
468  * hb_shape_plan_create_cached:
469  * @face: #hb_face_t to use
470  * @props: The #hb_segment_properties_t of the segment
471  * @user_features: (array length=num_user_features): The list of user-selected features
472  * @num_user_features: The number of user-selected features
473  * @shaper_list: (array zero-terminated=1): List of shapers to try
474  *
475  * Creates a cached shaping plan suitable for reuse, for a combination
476  * of @face, @user_features, @props, and @shaper_list.
477  *
478  * Return value: (transfer full): The shaping plan
479  *
480  * Since: 0.9.7
481  **/
482 hb_shape_plan_t *
hb_shape_plan_create_cached(hb_face_t * face,const hb_segment_properties_t * props,const hb_feature_t * user_features,unsigned int num_user_features,const char * const * shaper_list)483 hb_shape_plan_create_cached (hb_face_t                     *face,
484                              const hb_segment_properties_t *props,
485                              const hb_feature_t            *user_features,
486                              unsigned int                   num_user_features,
487                              const char * const            *shaper_list)
488 {
489   return hb_shape_plan_create_cached2 (face, props,
490                                        user_features, num_user_features,
491                                        nullptr, 0,
492                                        shaper_list);
493 }
494 
495 /**
496  * hb_shape_plan_create_cached2:
497  * @face: #hb_face_t to use
498  * @props: The #hb_segment_properties_t of the segment
499  * @user_features: (array length=num_user_features): The list of user-selected features
500  * @num_user_features: The number of user-selected features
501  * @coords: (array length=num_coords): The list of variation-space coordinates
502  * @num_coords: The number of variation-space coordinates
503  * @shaper_list: (array zero-terminated=1): List of shapers to try
504  *
505  * The variable-font version of #hb_shape_plan_create_cached.
506  * Creates a cached shaping plan suitable for reuse, for a combination
507  * of @face, @user_features, @props, and @shaper_list, plus the
508  * variation-space coordinates @coords.
509  *
510  * Return value: (transfer full): The shaping plan
511  *
512  * Since: 1.4.0
513  **/
514 hb_shape_plan_t *
hb_shape_plan_create_cached2(hb_face_t * face,const hb_segment_properties_t * props,const hb_feature_t * user_features,unsigned int num_user_features,const int * coords,unsigned int num_coords,const char * const * shaper_list)515 hb_shape_plan_create_cached2 (hb_face_t                     *face,
516                               const hb_segment_properties_t *props,
517                               const hb_feature_t            *user_features,
518                               unsigned int                   num_user_features,
519                               const int                     *coords,
520                               unsigned int                   num_coords,
521                               const char * const            *shaper_list)
522 {
523   DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr,
524                   "face=%p num_features=%d shaper_list=%p",
525                   face,
526                   num_user_features,
527                   shaper_list);
528 
529 retry:
530   hb_face_t::plan_node_t *cached_plan_nodes = face->shape_plans;
531 
532   bool dont_cache = hb_object_is_inert (face);
533 
534   if (likely (!dont_cache))
535   {
536     hb_shape_plan_key_t key;
537     if (!key.init (false,
538                    face,
539                    props,
540                    user_features,
541                    num_user_features,
542                    coords,
543                    num_coords,
544                    shaper_list))
545       return hb_shape_plan_get_empty ();
546 
547     for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next)
548       if (node->shape_plan->key.equal (&key))
549       {
550         DEBUG_MSG_FUNC (SHAPE_PLAN, node->shape_plan, "fulfilled from cache");
551         return hb_shape_plan_reference (node->shape_plan);
552       }
553   }
554 
555   hb_shape_plan_t *shape_plan = hb_shape_plan_create2 (face, props,
556                                                        user_features, num_user_features,
557                                                        coords, num_coords,
558                                                        shaper_list);
559 
560   if (unlikely (dont_cache))
561     return shape_plan;
562 
563   hb_face_t::plan_node_t *node = (hb_face_t::plan_node_t *) calloc (1, sizeof (hb_face_t::plan_node_t));
564   if (unlikely (!node))
565     return shape_plan;
566 
567   node->shape_plan = shape_plan;
568   node->next = cached_plan_nodes;
569 
570   if (unlikely (!face->shape_plans.cmpexch (cached_plan_nodes, node)))
571   {
572     hb_shape_plan_destroy (shape_plan);
573     free (node);
574     goto retry;
575   }
576   DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, "inserted into cache");
577 
578   return hb_shape_plan_reference (shape_plan);
579 }
580