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 not used for shaping directly, but can be access to query
41 * certain information about how shaping will perform given a set of input
42 * parameters (script, language, direction, features, etc.)
43 * Most client would not need to deal with shape plans directly.
44 **/
45
46
47/*
48 * hb_shape_plan_key_t
49 */
50
51bool
52hb_shape_plan_key_t::init (bool copy,
53 hb_face_t *face,
54 const hb_segment_properties_t *props,
55 const hb_feature_t *user_features,
56 unsigned int num_user_features,
57 const int *coords,
58 unsigned int num_coords,
59 const char * const *shaper_list)
60{
61 hb_feature_t *features = nullptr;
62 if (copy && num_user_features && !(features = (hb_feature_t *) calloc (num_user_features, sizeof (hb_feature_t))))
63 goto bail;
64
65 this->props = *props;
66 this->num_user_features = num_user_features;
67 this->user_features = copy ? features : user_features;
68 if (copy && num_user_features)
69 {
70 memcpy (features, user_features, num_user_features * sizeof (hb_feature_t));
71 /* Make start/end uniform to easier catch bugs. */
72 for (unsigned int i = 0; i < num_user_features; i++)
73 {
74 if (features[0].start != HB_FEATURE_GLOBAL_START)
75 features[0].start = 1;
76 if (features[0].end != HB_FEATURE_GLOBAL_END)
77 features[0].end = 2;
78 }
79 }
80 this->shaper_func = nullptr;
81 this->shaper_name = nullptr;
82 this->ot.init (face, coords, num_coords);
83
84 /*
85 * Choose shaper.
86 */
87
88#define HB_SHAPER_PLAN(shaper) \
89 HB_STMT_START { \
90 if (face->data.shaper) \
91 { \
92 this->shaper_func = _hb_##shaper##_shape; \
93 this->shaper_name = #shaper; \
94 return true; \
95 } \
96 } HB_STMT_END
97
98 if (unlikely (shaper_list))
99 {
100 for (; *shaper_list; shaper_list++)
101 if (false)
102 ;
103#define HB_SHAPER_IMPLEMENT(shaper) \
104 else if (0 == strcmp (*shaper_list, #shaper)) \
105 HB_SHAPER_PLAN (shaper);
106#include "hb-shaper-list.hh"
107#undef HB_SHAPER_IMPLEMENT
108 }
109 else
110 {
111 const hb_shaper_entry_t *shapers = _hb_shapers_get ();
112 for (unsigned int i = 0; i < HB_SHAPERS_COUNT; i++)
113 if (false)
114 ;
115#define HB_SHAPER_IMPLEMENT(shaper) \
116 else if (shapers[i].func == _hb_##shaper##_shape) \
117 HB_SHAPER_PLAN (shaper);
118#include "hb-shaper-list.hh"
119#undef HB_SHAPER_IMPLEMENT
120 }
121#undef HB_SHAPER_PLAN
122
123bail:
124 ::free (features);
125 return false;
126}
127
128bool
129hb_shape_plan_key_t::user_features_match (const hb_shape_plan_key_t *other)
130{
131 if (this->num_user_features != other->num_user_features)
132 return false;
133 for (unsigned int i = 0; i < num_user_features; i++)
134 {
135 if (this->user_features[i].tag != other->user_features[i].tag ||
136 this->user_features[i].value != other->user_features[i].value ||
137 (this->user_features[i].start == HB_FEATURE_GLOBAL_START &&
138 this->user_features[i].end == HB_FEATURE_GLOBAL_END) !=
139 (other->user_features[i].start == HB_FEATURE_GLOBAL_START &&
140 other->user_features[i].end == HB_FEATURE_GLOBAL_END))
141 return false;
142 }
143 return true;
144}
145
146bool
147hb_shape_plan_key_t::equal (const hb_shape_plan_key_t *other)
148{
149 return hb_segment_properties_equal (&this->props, &other->props) &&
150 this->user_features_match (other) &&
151 this->ot.equal (&other->ot) &&
152 this->shaper_func == other->shaper_func;
153}
154
155
156/*
157 * hb_shape_plan_t
158 */
159
160
161/**
162 * hb_shape_plan_create: (Xconstructor)
163 * @face:
164 * @props:
165 * @user_features: (array length=num_user_features):
166 * @num_user_features:
167 * @shaper_list: (array zero-terminated=1):
168 *
169 *
170 *
171 * Return value: (transfer full):
172 *
173 * Since: 0.9.7
174 **/
175hb_shape_plan_t *
176hb_shape_plan_create (hb_face_t *face,
177 const hb_segment_properties_t *props,
178 const hb_feature_t *user_features,
179 unsigned int num_user_features,
180 const char * const *shaper_list)
181{
182 return hb_shape_plan_create2 (face, props,
183 user_features, num_user_features,
184 nullptr, 0,
185 shaper_list);
186}
187
188hb_shape_plan_t *
189hb_shape_plan_create2 (hb_face_t *face,
190 const hb_segment_properties_t *props,
191 const hb_feature_t *user_features,
192 unsigned int num_user_features,
193 const int *coords,
194 unsigned int num_coords,
195 const char * const *shaper_list)
196{
197 DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr,
198 "face=%p num_features=%d num_coords=%d shaper_list=%p",
199 face,
200 num_user_features,
201 num_coords,
202 shaper_list);
203
204 assert (props->direction != HB_DIRECTION_INVALID);
205
206 hb_shape_plan_t *shape_plan;
207
208 if (unlikely (!props))
209 goto bail;
210 if (!(shape_plan = hb_object_create<hb_shape_plan_t> ()))
211 goto bail;
212
213 if (unlikely (!face))
214 face = hb_face_get_empty ();
215 hb_face_make_immutable (face);
216 shape_plan->face_unsafe = face;
217
218 if (unlikely (!shape_plan->key.init (true,
219 face,
220 props,
221 user_features,
222 num_user_features,
223 coords,
224 num_coords,
225 shaper_list)))
226 goto bail2;
227 if (unlikely (!shape_plan->ot.init0 (face, &shape_plan->key)))
228 goto bail3;
229
230 return shape_plan;
231
232bail3:
233 shape_plan->key.free ();
234bail2:
235 free (shape_plan);
236bail:
237 return hb_shape_plan_get_empty ();
238}
239
240/**
241 * hb_shape_plan_get_empty:
242 *
243 *
244 *
245 * Return value: (transfer full):
246 *
247 * Since: 0.9.7
248 **/
249hb_shape_plan_t *
250hb_shape_plan_get_empty ()
251{
252 return const_cast<hb_shape_plan_t *> (&Null(hb_shape_plan_t));
253}
254
255/**
256 * hb_shape_plan_reference: (skip)
257 * @shape_plan: a shape plan.
258 *
259 *
260 *
261 * Return value: (transfer full):
262 *
263 * Since: 0.9.7
264 **/
265hb_shape_plan_t *
266hb_shape_plan_reference (hb_shape_plan_t *shape_plan)
267{
268 return hb_object_reference (shape_plan);
269}
270
271/**
272 * hb_shape_plan_destroy: (skip)
273 * @shape_plan: a shape plan.
274 *
275 *
276 *
277 * Since: 0.9.7
278 **/
279void
280hb_shape_plan_destroy (hb_shape_plan_t *shape_plan)
281{
282 if (!hb_object_destroy (shape_plan)) return;
283
284 shape_plan->ot.fini ();
285 shape_plan->key.free ();
286 free (shape_plan);
287}
288
289/**
290 * hb_shape_plan_set_user_data: (skip)
291 * @shape_plan: a shape plan.
292 * @key:
293 * @data:
294 * @destroy:
295 * @replace:
296 *
297 *
298 *
299 * Return value:
300 *
301 * Since: 0.9.7
302 **/
303hb_bool_t
304hb_shape_plan_set_user_data (hb_shape_plan_t *shape_plan,
305 hb_user_data_key_t *key,
306 void * data,
307 hb_destroy_func_t destroy,
308 hb_bool_t replace)
309{
310 return hb_object_set_user_data (shape_plan, key, data, destroy, replace);
311}
312
313/**
314 * hb_shape_plan_get_user_data: (skip)
315 * @shape_plan: a shape plan.
316 * @key:
317 *
318 *
319 *
320 * Return value: (transfer none):
321 *
322 * Since: 0.9.7
323 **/
324void *
325hb_shape_plan_get_user_data (hb_shape_plan_t *shape_plan,
326 hb_user_data_key_t *key)
327{
328 return hb_object_get_user_data (shape_plan, key);
329}
330
331/**
332 * hb_shape_plan_get_shaper:
333 * @shape_plan: a shape plan.
334 *
335 *
336 *
337 * Return value: (transfer none):
338 *
339 * Since: 0.9.7
340 **/
341const char *
342hb_shape_plan_get_shaper (hb_shape_plan_t *shape_plan)
343{
344 return shape_plan->key.shaper_name;
345}
346
347
348/**
349 * hb_shape_plan_execute:
350 * @shape_plan: a shape plan.
351 * @font: a font.
352 * @buffer: a buffer.
353 * @features: (array length=num_features):
354 * @num_features:
355 *
356 *
357 *
358 * Return value:
359 *
360 * Since: 0.9.7
361 **/
362hb_bool_t
363hb_shape_plan_execute (hb_shape_plan_t *shape_plan,
364 hb_font_t *font,
365 hb_buffer_t *buffer,
366 const hb_feature_t *features,
367 unsigned int num_features)
368{
369 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan,
370 "num_features=%d shaper_func=%p, shaper_name=%s",
371 num_features,
372 shape_plan->key.shaper_func,
373 shape_plan->key.shaper_name);
374
375 if (unlikely (!buffer->len))
376 return true;
377
378 assert (!hb_object_is_immutable (buffer));
379 assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE);
380
381 if (unlikely (hb_object_is_inert (shape_plan)))
382 return false;
383
384 assert (shape_plan->face_unsafe == font->face);
385 assert (hb_segment_properties_equal (&shape_plan->key.props, &buffer->props));
386
387#define HB_SHAPER_EXECUTE(shaper) \
388 HB_STMT_START { \
389 return font->data.shaper && \
390 _hb_##shaper##_shape (shape_plan, font, buffer, features, num_features); \
391 } HB_STMT_END
392
393 if (false)
394 ;
395#define HB_SHAPER_IMPLEMENT(shaper) \
396 else if (shape_plan->key.shaper_func == _hb_##shaper##_shape) \
397 HB_SHAPER_EXECUTE (shaper);
398#include "hb-shaper-list.hh"
399#undef HB_SHAPER_IMPLEMENT
400
401#undef HB_SHAPER_EXECUTE
402
403 return false;
404}
405
406
407/*
408 * Caching
409 */
410
411/**
412 * hb_shape_plan_create_cached:
413 * @face:
414 * @props:
415 * @user_features: (array length=num_user_features):
416 * @num_user_features:
417 * @shaper_list: (array zero-terminated=1):
418 *
419 *
420 *
421 * Return value: (transfer full):
422 *
423 * Since: 0.9.7
424 **/
425hb_shape_plan_t *
426hb_shape_plan_create_cached (hb_face_t *face,
427 const hb_segment_properties_t *props,
428 const hb_feature_t *user_features,
429 unsigned int num_user_features,
430 const char * const *shaper_list)
431{
432 return hb_shape_plan_create_cached2 (face, props,
433 user_features, num_user_features,
434 nullptr, 0,
435 shaper_list);
436}
437
438hb_shape_plan_t *
439hb_shape_plan_create_cached2 (hb_face_t *face,
440 const hb_segment_properties_t *props,
441 const hb_feature_t *user_features,
442 unsigned int num_user_features,
443 const int *coords,
444 unsigned int num_coords,
445 const char * const *shaper_list)
446{
447 DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr,
448 "face=%p num_features=%d shaper_list=%p",
449 face,
450 num_user_features,
451 shaper_list);
452
453retry:
454 hb_face_t::plan_node_t *cached_plan_nodes = face->shape_plans;
455
456 bool dont_cache = hb_object_is_inert (face);
457
458 if (likely (!dont_cache))
459 {
460 hb_shape_plan_key_t key;
461 if (!key.init (false,
462 face,
463 props,
464 user_features,
465 num_user_features,
466 coords,
467 num_coords,
468 shaper_list))
469 return hb_shape_plan_get_empty ();
470
471 for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next)
472 if (node->shape_plan->key.equal (&key))
473 {
474 DEBUG_MSG_FUNC (SHAPE_PLAN, node->shape_plan, "fulfilled from cache");
475 return hb_shape_plan_reference (node->shape_plan);
476 }
477 }
478
479 hb_shape_plan_t *shape_plan = hb_shape_plan_create2 (face, props,
480 user_features, num_user_features,
481 coords, num_coords,
482 shaper_list);
483
484 if (unlikely (dont_cache))
485 return shape_plan;
486
487 hb_face_t::plan_node_t *node = (hb_face_t::plan_node_t *) calloc (1, sizeof (hb_face_t::plan_node_t));
488 if (unlikely (!node))
489 return shape_plan;
490
491 node->shape_plan = shape_plan;
492 node->next = cached_plan_nodes;
493
494 if (unlikely (!face->shape_plans.cmpexch (cached_plan_nodes, node)))
495 {
496 hb_shape_plan_destroy (shape_plan);
497 free (node);
498 goto retry;
499 }
500 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, "inserted into cache");
501
502 return hb_shape_plan_reference (shape_plan);
503}
504