22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
23 * |
23 * |
24 * Google Author(s): Behdad Esfahbod |
24 * Google Author(s): Behdad Esfahbod |
25 */ |
25 */ |
26 |
26 |
27 #include "hb-private.hh" |
27 #include "hb.hh" |
28 #include "hb-debug.hh" |
28 #include "hb-shape-plan.hh" |
29 #include "hb-shape-plan-private.hh" |
29 #include "hb-shaper.hh" |
30 #include "hb-shaper-private.hh" |
30 #include "hb-font.hh" |
31 #include "hb-font-private.hh" |
31 #include "hb-buffer.hh" |
32 #include "hb-buffer-private.hh" |
32 |
33 |
33 |
34 |
34 /** |
35 static void |
35 * SECTION:hb-shape-plan |
36 hb_shape_plan_plan (hb_shape_plan_t *shape_plan, |
36 * @title: hb-shape-plan |
37 const hb_feature_t *user_features, |
37 * @short_description: Object representing a shaping plan |
38 unsigned int num_user_features, |
38 * @include: hb.h |
39 const int *coords, |
39 * |
40 unsigned int num_coords, |
40 * Shape plans are not used for shaping directly, but can be access to query |
41 const char * const *shaper_list) |
41 * certain information about how shaping will perform given a set of input |
42 { |
42 * parameters (script, language, direction, features, etc.) |
43 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, |
43 * Most client would not need to deal with shape plans directly. |
44 "num_features=%d num_coords=%d shaper_list=%p", |
44 **/ |
45 num_user_features, |
45 |
46 num_coords, |
46 |
47 shaper_list); |
47 /* |
48 |
48 * hb_shape_plan_key_t |
49 const hb_shaper_pair_t *shapers = _hb_shapers_get (); |
49 */ |
|
50 |
|
51 bool |
|
52 hb_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 */ |
50 |
87 |
51 #define HB_SHAPER_PLAN(shaper) \ |
88 #define HB_SHAPER_PLAN(shaper) \ |
52 HB_STMT_START { \ |
89 HB_STMT_START { \ |
53 if (hb_##shaper##_shaper_face_data_ensure (shape_plan->face_unsafe)) { \ |
90 if (face->data.shaper) \ |
54 HB_SHAPER_DATA (shaper, shape_plan) = \ |
91 { \ |
55 HB_SHAPER_DATA_CREATE_FUNC (shaper, shape_plan) (shape_plan, \ |
92 this->shaper_func = _hb_##shaper##_shape; \ |
56 user_features, num_user_features, \ |
93 this->shaper_name = #shaper; \ |
57 coords, num_coords); \ |
94 return true; \ |
58 shape_plan->shaper_func = _hb_##shaper##_shape; \ |
|
59 shape_plan->shaper_name = #shaper; \ |
|
60 return; \ |
|
61 } \ |
95 } \ |
62 } HB_STMT_END |
96 } HB_STMT_END |
63 |
97 |
64 if (likely (!shaper_list)) { |
98 if (unlikely (shaper_list)) |
65 for (unsigned int i = 0; i < HB_SHAPERS_COUNT; i++) |
99 { |
66 if (0) |
|
67 ; |
|
68 #define HB_SHAPER_IMPLEMENT(shaper) \ |
|
69 else if (shapers[i].func == _hb_##shaper##_shape) \ |
|
70 HB_SHAPER_PLAN (shaper); |
|
71 #include "hb-shaper-list.hh" |
|
72 #undef HB_SHAPER_IMPLEMENT |
|
73 } else { |
|
74 for (; *shaper_list; shaper_list++) |
100 for (; *shaper_list; shaper_list++) |
75 if (0) |
101 if (false) |
76 ; |
102 ; |
77 #define HB_SHAPER_IMPLEMENT(shaper) \ |
103 #define HB_SHAPER_IMPLEMENT(shaper) \ |
78 else if (0 == strcmp (*shaper_list, #shaper)) \ |
104 else if (0 == strcmp (*shaper_list, #shaper)) \ |
79 HB_SHAPER_PLAN (shaper); |
105 HB_SHAPER_PLAN (shaper); |
80 #include "hb-shaper-list.hh" |
106 #include "hb-shaper-list.hh" |
81 #undef HB_SHAPER_IMPLEMENT |
107 #undef HB_SHAPER_IMPLEMENT |
82 } |
108 } |
83 |
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 } |
84 #undef HB_SHAPER_PLAN |
121 #undef HB_SHAPER_PLAN |
|
122 |
|
123 bail: |
|
124 ::free (features); |
|
125 return false; |
|
126 } |
|
127 |
|
128 bool |
|
129 hb_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 |
|
146 bool |
|
147 hb_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; |
85 } |
153 } |
86 |
154 |
87 |
155 |
88 /* |
156 /* |
89 * hb_shape_plan_t |
157 * hb_shape_plan_t |
90 */ |
158 */ |
|
159 |
91 |
160 |
92 /** |
161 /** |
93 * hb_shape_plan_create: (Xconstructor) |
162 * hb_shape_plan_create: (Xconstructor) |
94 * @face: |
163 * @face: |
95 * @props: |
164 * @props: |
119 hb_shape_plan_t * |
188 hb_shape_plan_t * |
120 hb_shape_plan_create2 (hb_face_t *face, |
189 hb_shape_plan_create2 (hb_face_t *face, |
121 const hb_segment_properties_t *props, |
190 const hb_segment_properties_t *props, |
122 const hb_feature_t *user_features, |
191 const hb_feature_t *user_features, |
123 unsigned int num_user_features, |
192 unsigned int num_user_features, |
124 const int *orig_coords, |
193 const int *coords, |
125 unsigned int num_coords, |
194 unsigned int num_coords, |
126 const char * const *shaper_list) |
195 const char * const *shaper_list) |
127 { |
196 { |
128 DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr, |
197 DEBUG_MSG_FUNC (SHAPE_PLAN, nullptr, |
129 "face=%p num_features=%d num_coords=%d shaper_list=%p", |
198 "face=%p num_features=%d num_coords=%d shaper_list=%p", |
130 face, |
199 face, |
131 num_user_features, |
200 num_user_features, |
132 num_coords, |
201 num_coords, |
133 shaper_list); |
202 shaper_list); |
134 |
203 |
|
204 assert (props->direction != HB_DIRECTION_INVALID); |
|
205 |
135 hb_shape_plan_t *shape_plan; |
206 hb_shape_plan_t *shape_plan; |
136 hb_feature_t *features = nullptr; |
207 |
137 int *coords = nullptr; |
208 if (unlikely (!props)) |
|
209 goto bail; |
|
210 if (!(shape_plan = hb_object_create<hb_shape_plan_t> ())) |
|
211 goto bail; |
138 |
212 |
139 if (unlikely (!face)) |
213 if (unlikely (!face)) |
140 face = hb_face_get_empty (); |
214 face = hb_face_get_empty (); |
141 if (unlikely (!props)) |
|
142 return hb_shape_plan_get_empty (); |
|
143 if (num_user_features && !(features = (hb_feature_t *) calloc (num_user_features, sizeof (hb_feature_t)))) |
|
144 return hb_shape_plan_get_empty (); |
|
145 if (num_coords && !(coords = (int *) calloc (num_coords, sizeof (int)))) |
|
146 { |
|
147 free (features); |
|
148 return hb_shape_plan_get_empty (); |
|
149 } |
|
150 if (!(shape_plan = hb_object_create<hb_shape_plan_t> ())) |
|
151 { |
|
152 free (coords); |
|
153 free (features); |
|
154 return hb_shape_plan_get_empty (); |
|
155 } |
|
156 |
|
157 assert (props->direction != HB_DIRECTION_INVALID); |
|
158 |
|
159 hb_face_make_immutable (face); |
215 hb_face_make_immutable (face); |
160 shape_plan->default_shaper_list = !shaper_list; |
|
161 shape_plan->face_unsafe = face; |
216 shape_plan->face_unsafe = face; |
162 shape_plan->props = *props; |
217 |
163 shape_plan->num_user_features = num_user_features; |
218 if (unlikely (!shape_plan->key.init (true, |
164 shape_plan->user_features = features; |
219 face, |
165 if (num_user_features) |
220 props, |
166 memcpy (features, user_features, num_user_features * sizeof (hb_feature_t)); |
221 user_features, |
167 shape_plan->num_coords = num_coords; |
222 num_user_features, |
168 shape_plan->coords = coords; |
223 coords, |
169 if (num_coords) |
224 num_coords, |
170 memcpy (coords, orig_coords, num_coords * sizeof (int)); |
225 shaper_list))) |
171 |
226 goto bail2; |
172 hb_shape_plan_plan (shape_plan, |
227 if (unlikely (!shape_plan->ot.init0 (face, &shape_plan->key))) |
173 user_features, num_user_features, |
228 goto bail3; |
174 coords, num_coords, |
|
175 shaper_list); |
|
176 |
229 |
177 return shape_plan; |
230 return shape_plan; |
|
231 |
|
232 bail3: |
|
233 shape_plan->key.free (); |
|
234 bail2: |
|
235 free (shape_plan); |
|
236 bail: |
|
237 return hb_shape_plan_get_empty (); |
178 } |
238 } |
179 |
239 |
180 /** |
240 /** |
181 * hb_shape_plan_get_empty: |
241 * hb_shape_plan_get_empty: |
182 * |
242 * |
319 unsigned int num_features) |
367 unsigned int num_features) |
320 { |
368 { |
321 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, |
369 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, |
322 "num_features=%d shaper_func=%p, shaper_name=%s", |
370 "num_features=%d shaper_func=%p, shaper_name=%s", |
323 num_features, |
371 num_features, |
324 shape_plan->shaper_func, |
372 shape_plan->key.shaper_func, |
325 shape_plan->shaper_name); |
373 shape_plan->key.shaper_name); |
326 |
374 |
327 if (unlikely (!buffer->len)) |
375 if (unlikely (!buffer->len)) |
328 return true; |
376 return true; |
329 |
377 |
330 assert (!hb_object_is_inert (buffer)); |
378 assert (!hb_object_is_immutable (buffer)); |
331 assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE); |
379 assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_UNICODE); |
332 |
380 |
333 if (unlikely (hb_object_is_inert (shape_plan))) |
381 if (unlikely (hb_object_is_inert (shape_plan))) |
334 return false; |
382 return false; |
335 |
383 |
336 assert (shape_plan->face_unsafe == font->face); |
384 assert (shape_plan->face_unsafe == font->face); |
337 assert (hb_segment_properties_equal (&shape_plan->props, &buffer->props)); |
385 assert (hb_segment_properties_equal (&shape_plan->key.props, &buffer->props)); |
338 |
386 |
339 #define HB_SHAPER_EXECUTE(shaper) \ |
387 #define HB_SHAPER_EXECUTE(shaper) \ |
340 HB_STMT_START { \ |
388 HB_STMT_START { \ |
341 return HB_SHAPER_DATA (shaper, shape_plan) && \ |
389 return font->data.shaper && \ |
342 hb_##shaper##_shaper_font_data_ensure (font) && \ |
|
343 _hb_##shaper##_shape (shape_plan, font, buffer, features, num_features); \ |
390 _hb_##shaper##_shape (shape_plan, font, buffer, features, num_features); \ |
344 } HB_STMT_END |
391 } HB_STMT_END |
345 |
392 |
346 if (0) |
393 if (false) |
347 ; |
394 ; |
348 #define HB_SHAPER_IMPLEMENT(shaper) \ |
395 #define HB_SHAPER_IMPLEMENT(shaper) \ |
349 else if (shape_plan->shaper_func == _hb_##shaper##_shape) \ |
396 else if (shape_plan->key.shaper_func == _hb_##shaper##_shape) \ |
350 HB_SHAPER_EXECUTE (shaper); |
397 HB_SHAPER_EXECUTE (shaper); |
351 #include "hb-shaper-list.hh" |
398 #include "hb-shaper-list.hh" |
352 #undef HB_SHAPER_IMPLEMENT |
399 #undef HB_SHAPER_IMPLEMENT |
353 |
400 |
354 #undef HB_SHAPER_EXECUTE |
401 #undef HB_SHAPER_EXECUTE |
356 return false; |
403 return false; |
357 } |
404 } |
358 |
405 |
359 |
406 |
360 /* |
407 /* |
361 * caching |
408 * Caching |
362 */ |
409 */ |
363 |
|
364 #if 0 |
|
365 static unsigned int |
|
366 hb_shape_plan_hash (const hb_shape_plan_t *shape_plan) |
|
367 { |
|
368 return hb_segment_properties_hash (&shape_plan->props) + |
|
369 shape_plan->default_shaper_list ? 0 : (intptr_t) shape_plan->shaper_func; |
|
370 } |
|
371 #endif |
|
372 |
|
373 /* User-feature caching is currently somewhat dumb: |
|
374 * it only finds matches where the feature array is identical, |
|
375 * not cases where the feature lists would be compatible for plan purposes |
|
376 * but have different ranges, for example. |
|
377 */ |
|
378 struct hb_shape_plan_proposal_t |
|
379 { |
|
380 const hb_segment_properties_t props; |
|
381 const char * const *shaper_list; |
|
382 const hb_feature_t *user_features; |
|
383 unsigned int num_user_features; |
|
384 const int *coords; |
|
385 unsigned int num_coords; |
|
386 hb_shape_func_t *shaper_func; |
|
387 }; |
|
388 |
|
389 static inline hb_bool_t |
|
390 hb_shape_plan_user_features_match (const hb_shape_plan_t *shape_plan, |
|
391 const hb_shape_plan_proposal_t *proposal) |
|
392 { |
|
393 if (proposal->num_user_features != shape_plan->num_user_features) |
|
394 return false; |
|
395 for (unsigned int i = 0, n = proposal->num_user_features; i < n; i++) |
|
396 if (proposal->user_features[i].tag != shape_plan->user_features[i].tag || |
|
397 proposal->user_features[i].value != shape_plan->user_features[i].value || |
|
398 proposal->user_features[i].start != shape_plan->user_features[i].start || |
|
399 proposal->user_features[i].end != shape_plan->user_features[i].end) |
|
400 return false; |
|
401 return true; |
|
402 } |
|
403 |
|
404 static inline hb_bool_t |
|
405 hb_shape_plan_coords_match (const hb_shape_plan_t *shape_plan, |
|
406 const hb_shape_plan_proposal_t *proposal) |
|
407 { |
|
408 if (proposal->num_coords != shape_plan->num_coords) |
|
409 return false; |
|
410 for (unsigned int i = 0, n = proposal->num_coords; i < n; i++) |
|
411 if (proposal->coords[i] != shape_plan->coords[i]) |
|
412 return false; |
|
413 return true; |
|
414 } |
|
415 |
|
416 static hb_bool_t |
|
417 hb_shape_plan_matches (const hb_shape_plan_t *shape_plan, |
|
418 const hb_shape_plan_proposal_t *proposal) |
|
419 { |
|
420 return hb_segment_properties_equal (&shape_plan->props, &proposal->props) && |
|
421 hb_shape_plan_user_features_match (shape_plan, proposal) && |
|
422 hb_shape_plan_coords_match (shape_plan, proposal) && |
|
423 ((shape_plan->default_shaper_list && !proposal->shaper_list) || |
|
424 (shape_plan->shaper_func == proposal->shaper_func)); |
|
425 } |
|
426 |
|
427 static inline hb_bool_t |
|
428 hb_non_global_user_features_present (const hb_feature_t *user_features, |
|
429 unsigned int num_user_features) |
|
430 { |
|
431 while (num_user_features) { |
|
432 if (user_features->start != 0 || user_features->end != (unsigned int) -1) |
|
433 return true; |
|
434 num_user_features--; |
|
435 user_features++; |
|
436 } |
|
437 return false; |
|
438 } |
|
439 |
|
440 static inline hb_bool_t |
|
441 hb_coords_present (const int *coords, |
|
442 unsigned int num_coords) |
|
443 { |
|
444 return num_coords != 0; |
|
445 } |
|
446 |
410 |
447 /** |
411 /** |
448 * hb_shape_plan_create_cached: |
412 * hb_shape_plan_create_cached: |
449 * @face: |
413 * @face: |
450 * @props: |
414 * @props: |
484 "face=%p num_features=%d shaper_list=%p", |
448 "face=%p num_features=%d shaper_list=%p", |
485 face, |
449 face, |
486 num_user_features, |
450 num_user_features, |
487 shaper_list); |
451 shaper_list); |
488 |
452 |
489 hb_shape_plan_proposal_t proposal = { |
453 retry: |
490 *props, |
454 hb_face_t::plan_node_t *cached_plan_nodes = face->shape_plans; |
491 shaper_list, |
455 |
492 user_features, |
456 bool dont_cache = hb_object_is_inert (face); |
493 num_user_features, |
457 |
494 nullptr |
458 if (likely (!dont_cache)) |
495 }; |
459 { |
496 |
460 hb_shape_plan_key_t key; |
497 if (shaper_list) { |
461 if (!key.init (false, |
498 /* Choose shaper. Adapted from hb_shape_plan_plan(). |
462 face, |
499 * Must choose shaper exactly the same way as that function. */ |
463 props, |
500 for (const char * const *shaper_item = shaper_list; *shaper_item; shaper_item++) |
464 user_features, |
501 if (0) |
465 num_user_features, |
502 ; |
466 coords, |
503 #define HB_SHAPER_IMPLEMENT(shaper) \ |
467 num_coords, |
504 else if (0 == strcmp (*shaper_item, #shaper) && \ |
468 shaper_list)) |
505 hb_##shaper##_shaper_face_data_ensure (face)) \ |
|
506 { \ |
|
507 proposal.shaper_func = _hb_##shaper##_shape; \ |
|
508 break; \ |
|
509 } |
|
510 #include "hb-shaper-list.hh" |
|
511 #undef HB_SHAPER_IMPLEMENT |
|
512 |
|
513 if (unlikely (!proposal.shaper_func)) |
|
514 return hb_shape_plan_get_empty (); |
469 return hb_shape_plan_get_empty (); |
515 } |
470 |
516 |
|
517 |
|
518 retry: |
|
519 hb_face_t::plan_node_t *cached_plan_nodes = (hb_face_t::plan_node_t *) hb_atomic_ptr_get (&face->shape_plans); |
|
520 |
|
521 /* Don't look for plan in the cache if there were variation coordinates XXX Fix me. */ |
|
522 if (!hb_coords_present (coords, num_coords)) |
|
523 for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next) |
471 for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next) |
524 if (hb_shape_plan_matches (node->shape_plan, &proposal)) |
472 if (node->shape_plan->key.equal (&key)) |
525 { |
473 { |
526 DEBUG_MSG_FUNC (SHAPE_PLAN, node->shape_plan, "fulfilled from cache"); |
474 DEBUG_MSG_FUNC (SHAPE_PLAN, node->shape_plan, "fulfilled from cache"); |
527 return hb_shape_plan_reference (node->shape_plan); |
475 return hb_shape_plan_reference (node->shape_plan); |
528 } |
476 } |
529 |
477 } |
530 /* Not found. */ |
478 |
531 hb_shape_plan_t *shape_plan = hb_shape_plan_create2 (face, props, |
479 hb_shape_plan_t *shape_plan = hb_shape_plan_create2 (face, props, |
532 user_features, num_user_features, |
480 user_features, num_user_features, |
533 coords, num_coords, |
481 coords, num_coords, |
534 shaper_list); |
482 shaper_list); |
535 |
483 |
536 /* Don't add to the cache if face is inert. */ |
484 if (unlikely (dont_cache)) |
537 if (unlikely (hb_object_is_inert (face))) |
|
538 return shape_plan; |
|
539 |
|
540 /* Don't add the plan to the cache if there were user features with non-global ranges */ |
|
541 if (hb_non_global_user_features_present (user_features, num_user_features)) |
|
542 return shape_plan; |
|
543 /* Don't add the plan to the cache if there were variation coordinates XXX Fix me. */ |
|
544 if (hb_coords_present (coords, num_coords)) |
|
545 return shape_plan; |
485 return shape_plan; |
546 |
486 |
547 hb_face_t::plan_node_t *node = (hb_face_t::plan_node_t *) calloc (1, sizeof (hb_face_t::plan_node_t)); |
487 hb_face_t::plan_node_t *node = (hb_face_t::plan_node_t *) calloc (1, sizeof (hb_face_t::plan_node_t)); |
548 if (unlikely (!node)) |
488 if (unlikely (!node)) |
549 return shape_plan; |
489 return shape_plan; |
550 |
490 |
551 node->shape_plan = shape_plan; |
491 node->shape_plan = shape_plan; |
552 node->next = cached_plan_nodes; |
492 node->next = cached_plan_nodes; |
553 |
493 |
554 if (!hb_atomic_ptr_cmpexch (&face->shape_plans, cached_plan_nodes, node)) { |
494 if (unlikely (!face->shape_plans.cmpexch (cached_plan_nodes, node))) |
|
495 { |
555 hb_shape_plan_destroy (shape_plan); |
496 hb_shape_plan_destroy (shape_plan); |
556 free (node); |
497 free (node); |
557 goto retry; |
498 goto retry; |
558 } |
499 } |
559 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, "inserted into cache"); |
500 DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, "inserted into cache"); |
560 |
501 |
561 return hb_shape_plan_reference (shape_plan); |
502 return hb_shape_plan_reference (shape_plan); |
562 } |
503 } |
563 |
|
564 /** |
|
565 * hb_shape_plan_get_shaper: |
|
566 * @shape_plan: a shape plan. |
|
567 * |
|
568 * |
|
569 * |
|
570 * Return value: (transfer none): |
|
571 * |
|
572 * Since: 0.9.7 |
|
573 **/ |
|
574 const char * |
|
575 hb_shape_plan_get_shaper (hb_shape_plan_t *shape_plan) |
|
576 { |
|
577 return shape_plan->shaper_name; |
|
578 } |
|