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