Coverage Report

Created: 2025-07-07 10:01

/work/workdir/UnpackedTarball/harfbuzz/src/hb-shape.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright © 2009  Red Hat, Inc.
3
 * Copyright © 2012  Google, Inc.
4
 *
5
 *  This is part of HarfBuzz, a text shaping library.
6
 *
7
 * Permission is hereby granted, without written agreement and without
8
 * license or royalty fees, to use, copy, modify, and distribute this
9
 * software and its documentation for any purpose, provided that the
10
 * above copyright notice and the following two paragraphs appear in
11
 * all copies of this software.
12
 *
13
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
14
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
15
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
16
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
17
 * DAMAGE.
18
 *
19
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
20
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
22
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
23
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24
 *
25
 * Red Hat Author(s): Behdad Esfahbod
26
 * Google Author(s): Behdad Esfahbod
27
 */
28
29
#include "hb.hh"
30
31
#include "hb-shaper.hh"
32
#include "hb-shape-plan.hh"
33
#include "hb-buffer.hh"
34
#include "hb-font.hh"
35
#include "hb-machinery.hh"
36
37
38
#ifndef HB_NO_SHAPER
39
40
/**
41
 * SECTION:hb-shape
42
 * @title: hb-shape
43
 * @short_description: Conversion of text strings into positioned glyphs
44
 * @include: hb.h
45
 *
46
 * Shaping is the central operation of HarfBuzz. Shaping operates on buffers,
47
 * which are sequences of Unicode characters that use the same font and have
48
 * the same text direction, script, and language. After shaping the buffer
49
 * contains the output glyphs and their positions.
50
 **/
51
52
53
static inline void free_static_shaper_list ();
54
55
static const char * const nil_shaper_list[] = {nullptr};
56
57
static struct hb_shaper_list_lazy_loader_t : hb_lazy_loader_t<const char *,
58
                    hb_shaper_list_lazy_loader_t>
59
{
60
  static const char ** create ()
61
0
  {
62
0
    const char **shaper_list = (const char **) hb_calloc (1 + HB_SHAPERS_COUNT, sizeof (const char *));
63
0
    if (unlikely (!shaper_list))
64
0
      return nullptr;
65
66
0
    const hb_shaper_entry_t *shapers = _hb_shapers_get ();
67
0
    unsigned int i;
68
0
    for (i = 0; i < HB_SHAPERS_COUNT; i++)
69
0
      shaper_list[i] = shapers[i].name;
70
0
    shaper_list[i] = nullptr;
71
72
0
    hb_atexit (free_static_shaper_list);
73
74
0
    return shaper_list;
75
0
  }
76
  static void destroy (const char **l)
77
0
  { hb_free (l); }
78
  static const char * const * get_null ()
79
0
  { return nil_shaper_list; }
80
} static_shaper_list;
81
82
static inline
83
void free_static_shaper_list ()
84
0
{
85
0
  static_shaper_list.free_instance ();
86
0
}
87
88
89
/**
90
 * hb_shape_list_shapers:
91
 *
92
 * Retrieves the list of shapers supported by HarfBuzz.
93
 *
94
 * Return value: (transfer none) (array zero-terminated=1): a
95
 *    `NULL`-terminated array of supported shapers constant string.
96
 *    The returned array is owned by HarfBuzz and should not be
97
 *    modified or freed.
98
 *
99
 * Since: 0.9.2
100
 **/
101
const char **
102
hb_shape_list_shapers ()
103
0
{
104
0
  return static_shaper_list.get_unconst ();
105
0
}
106
107
108
/**
109
 * hb_shape_full:
110
 * @font: an #hb_font_t to use for shaping
111
 * @buffer: an #hb_buffer_t to shape
112
 * @features: (array length=num_features) (nullable): an array of user
113
 *    specified #hb_feature_t or `NULL`
114
 * @num_features: the length of @features array
115
 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated
116
 *    array of shapers to use or `NULL`
117
 *
118
 * See hb_shape() for details. If @shaper_list is not `NULL`, the specified
119
 * shapers will be used in the given order, otherwise the default shapers list
120
 * will be used.
121
 *
122
 * Return value: false if all shapers failed, true otherwise
123
 *
124
 * Since: 0.9.2
125
 **/
126
hb_bool_t
127
hb_shape_full (hb_font_t          *font,
128
         hb_buffer_t        *buffer,
129
         const hb_feature_t *features,
130
         unsigned int        num_features,
131
         const char * const *shaper_list)
132
32.9M
{
133
32.9M
  if (unlikely (!buffer->len))
134
0
    return true;
135
136
32.9M
  buffer->enter ();
137
138
32.9M
  hb_buffer_t *text_buffer = nullptr;
139
32.9M
  if (buffer->flags & HB_BUFFER_FLAG_VERIFY)
140
0
  {
141
0
    text_buffer = hb_buffer_create ();
142
0
    hb_buffer_append (text_buffer, buffer, 0, -1);
143
0
  }
144
145
32.9M
  hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached2 (font->face, &buffer->props,
146
32.9M
                    features, num_features,
147
32.9M
                    font->coords, font->num_coords,
148
32.9M
                    shaper_list);
149
150
32.9M
  hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
151
152
32.9M
  if (buffer->max_ops <= 0)
153
0
    buffer->shaping_failed = true;
154
155
32.9M
  hb_shape_plan_destroy (shape_plan);
156
157
32.9M
  if (text_buffer)
158
0
  {
159
0
    if (res && buffer->successful && !buffer->shaping_failed
160
0
      && text_buffer->successful
161
0
      && !buffer->verify (text_buffer,
162
0
        font,
163
0
        features,
164
0
        num_features,
165
0
        shaper_list))
166
0
      res = false;
167
0
    hb_buffer_destroy (text_buffer);
168
0
  }
169
170
32.9M
  buffer->leave ();
171
172
32.9M
  return res;
173
32.9M
}
174
175
/**
176
 * hb_shape:
177
 * @font: an #hb_font_t to use for shaping
178
 * @buffer: an #hb_buffer_t to shape
179
 * @features: (array length=num_features) (nullable): an array of user
180
 *    specified #hb_feature_t or `NULL`
181
 * @num_features: the length of @features array
182
 *
183
 * Shapes @buffer using @font turning its Unicode characters content to
184
 * positioned glyphs. If @features is not `NULL`, it will be used to control the
185
 * features applied during shaping. If two @features have the same tag but
186
 * overlapping ranges the value of the feature with the higher index takes
187
 * precedence.
188
 *
189
 * Since: 0.9.2
190
 **/
191
void
192
hb_shape (hb_font_t           *font,
193
    hb_buffer_t         *buffer,
194
    const hb_feature_t  *features,
195
    unsigned int         num_features)
196
0
{
197
0
  hb_shape_full (font, buffer, features, num_features, nullptr);
198
0
}
199
200
201
#ifdef HB_EXPERIMENTAL_API
202
203
static float
204
buffer_advance (hb_buffer_t *buffer)
205
{
206
  float a = 0;
207
  auto *pos = buffer->pos;
208
  unsigned count = buffer->len;
209
  if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
210
    for (unsigned i = 0; i < count; i++)
211
      a += pos[i].x_advance;
212
  else
213
    for (unsigned i = 0; i < count; i++)
214
      a += pos[i].y_advance;
215
  return a;
216
}
217
218
static void
219
reset_buffer (hb_buffer_t *buffer,
220
        hb_array_t<const hb_glyph_info_t> text)
221
{
222
  assert (buffer->ensure (text.length));
223
  buffer->have_positions = false;
224
  buffer->len = text.length;
225
  hb_memcpy (buffer->info, text.arrayZ, text.length * sizeof (buffer->info[0]));
226
  hb_buffer_set_content_type (buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
227
}
228
229
/**
230
 * hb_shape_justify:
231
 * @font: a mutable #hb_font_t to use for shaping
232
 * @buffer: an #hb_buffer_t to shape
233
 * @features: (array length=num_features) (nullable): an array of user
234
 *    specified #hb_feature_t or `NULL`
235
 * @num_features: the length of @features array
236
 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated
237
 *    array of shapers to use or `NULL`
238
 * @min_target_advance: Minimum advance width/height to aim for.
239
 * @max_target_advance: Maximum advance width/height to aim for.
240
 * @advance: (inout): Input/output advance width/height of the buffer.
241
 * @var_tag: (out): Variation-axis tag used for justification.
242
 * @var_value: (out): Variation-axis value used to reach target justification.
243
 *
244
 * See hb_shape_full() for basic details. If @shaper_list is not `NULL`, the specified
245
 * shapers will be used in the given order, otherwise the default shapers list
246
 * will be used.
247
 *
248
 * In addition, justify the shaping results such that the shaping results reach
249
 * the target advance width/height, depending on the buffer direction.
250
 *
251
 * If the advance of the buffer shaped with hb_shape_full() is already known,
252
 * put that in *advance. Otherwise set *advance to zero.
253
 *
254
 * This API is currently experimental and will probably change in the future.
255
 *
256
 * Return value: false if all shapers failed, true otherwise
257
 *
258
 * XSince: EXPERIMENTAL
259
 **/
260
hb_bool_t
261
hb_shape_justify (hb_font_t          *font,
262
      hb_buffer_t        *buffer,
263
      const hb_feature_t *features,
264
      unsigned int        num_features,
265
      const char * const *shaper_list,
266
      float               min_target_advance,
267
      float               max_target_advance,
268
      float              *advance, /* IN/OUT */
269
      hb_tag_t           *var_tag, /* OUT */
270
      float              *var_value /* OUT */)
271
{
272
  // TODO Negative font scales?
273
274
  /* If default advance already matches target, nothing to do. Shape and return. */
275
  if (min_target_advance <= *advance && *advance <= max_target_advance)
276
  {
277
    *var_tag = HB_TAG_NONE;
278
    *var_value = 0.0f;
279
    return hb_shape_full (font, buffer,
280
        features, num_features,
281
        shaper_list);
282
  }
283
284
  hb_face_t *face = font->face;
285
286
  /* Choose variation tag to use for justification. */
287
288
  hb_tag_t tag = HB_TAG_NONE;
289
  hb_ot_var_axis_info_t axis_info;
290
291
  hb_tag_t tags[] =
292
  {
293
    HB_TAG ('j','s','t','f'),
294
    HB_TAG ('w','d','t','h'),
295
  };
296
  for (unsigned i = 0; i < ARRAY_LENGTH (tags); i++)
297
    if (hb_ot_var_find_axis_info (face, tags[i], &axis_info))
298
    {
299
      tag = *var_tag = tags[i];
300
      break;
301
    }
302
303
  /* If no suitable variation axis found, can't justify.  Just shape and return. */
304
  if (!tag)
305
  {
306
    *var_tag = HB_TAG_NONE;
307
    *var_value = 0.0f;
308
    if (hb_shape_full (font, buffer,
309
           features, num_features,
310
           shaper_list))
311
    {
312
      *advance = buffer_advance (buffer);
313
      return true;
314
    }
315
    else
316
      return false;
317
  }
318
319
  /* Copy buffer text as we need it so we can shape multiple times. */
320
  unsigned text_len = buffer->len;
321
  auto *text_info = (hb_glyph_info_t *) hb_malloc (text_len * sizeof (buffer->info[0]));
322
  if (unlikely (text_len && !text_info))
323
    return false;
324
  hb_memcpy (text_info, buffer->info, text_len * sizeof (buffer->info[0]));
325
  auto text = hb_array<const hb_glyph_info_t> (text_info, text_len);
326
327
  /* If default advance was not provided to us, calculate it. */
328
  if (!*advance)
329
  {
330
    hb_font_set_variation (font, tag, axis_info.default_value);
331
    if (!hb_shape_full (font, buffer,
332
      features, num_features,
333
      shaper_list))
334
      return false;
335
    *advance = buffer_advance (buffer);
336
  }
337
338
  /* If default advance already matches target, nothing to do. Shape and return.
339
   * Do this again, in case advance was just calculated.
340
   */
341
  if (min_target_advance <= *advance && *advance <= max_target_advance)
342
  {
343
    *var_tag = HB_TAG_NONE;
344
    *var_value = 0.0f;
345
    return true;
346
  }
347
348
  /* Prepare for running the solver. */
349
  double a, b, ya, yb;
350
  if (*advance < min_target_advance)
351
  {
352
    /* Need to expand. */
353
    ya = (double) *advance;
354
    a = (double) axis_info.default_value;
355
    b = (double) axis_info.max_value;
356
357
    /* Shape buffer for maximum expansion to use as other
358
     * starting point for the solver. */
359
    hb_font_set_variation (font, tag, (float) b);
360
    reset_buffer (buffer, text);
361
    if (!hb_shape_full (font, buffer,
362
      features, num_features,
363
      shaper_list))
364
      return false;
365
    yb = (double) buffer_advance (buffer);
366
    /* If the maximum expansion is less than max target,
367
     * there's nothing to solve for. Just return it. */
368
    if (yb <= (double) max_target_advance)
369
    {
370
      *var_value = (float) b;
371
      *advance = (float) yb;
372
      return true;
373
    }
374
  }
375
  else
376
  {
377
    /* Need to shrink. */
378
    yb = (double) *advance;
379
    a = (double) axis_info.min_value;
380
    b = (double) axis_info.default_value;
381
382
    /* Shape buffer for maximum shrinkate to use as other
383
     * starting point for the solver. */
384
    hb_font_set_variation (font, tag, (float) a);
385
    reset_buffer (buffer, text);
386
    if (!hb_shape_full (font, buffer,
387
      features, num_features,
388
      shaper_list))
389
      return false;
390
    ya = (double) buffer_advance (buffer);
391
    /* If the maximum shrinkate is more than min target,
392
     * there's nothing to solve for. Just return it. */
393
    if (ya >= (double) min_target_advance)
394
    {
395
      *var_value = (float) a;
396
      *advance = (float) ya;
397
      return true;
398
    }
399
  }
400
401
  /* Run the solver to find a var axis value that hits
402
   * the desired width. */
403
404
  double epsilon = (b - a) / (1<<14);
405
  bool failed = false;
406
407
  auto f = [&] (double x)
408
  {
409
    hb_font_set_variation (font, tag, (float) x);
410
    reset_buffer (buffer, text);
411
    if (unlikely (!hb_shape_full (font, buffer,
412
          features, num_features,
413
          shaper_list)))
414
    {
415
      failed = true;
416
      return (double) min_target_advance;
417
    }
418
419
    double w = (double) buffer_advance (buffer);
420
    DEBUG_MSG (JUSTIFY, nullptr, "Trying '%c%c%c%c' axis parameter %f. Advance %g. Target: min %g max %g",
421
         HB_UNTAG (tag), x, w,
422
         (double) min_target_advance, (double) max_target_advance);
423
    return w;
424
  };
425
426
  double y = 0;
427
  double itp = solve_itp (f,
428
        a, b,
429
        epsilon,
430
        (double) min_target_advance, (double) max_target_advance,
431
        ya, yb, y);
432
433
  hb_free (text_info);
434
435
  if (failed)
436
    return false;
437
438
  *var_value = (float) itp;
439
  *advance = (float) y;
440
441
  return true;
442
}
443
444
#endif
445
446
447
#endif