Coverage Report

Created: 2026-10-16 07:15

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