Coverage Report

Created: 2025-09-05 07:06

/src/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
118k
{
133
118k
  if (unlikely (!buffer->len))
134
0
    return true;
135
136
118k
  buffer->enter ();
137
138
118k
  hb_buffer_t *text_buffer = nullptr;
139
118k
  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
118k
  hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached2 (font->face, &buffer->props,
146
118k
                    features, num_features,
147
118k
                    font->coords, font->num_coords,
148
118k
                    shaper_list);
149
150
118k
  hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
151
152
118k
  hb_shape_plan_destroy (shape_plan);
153
154
118k
  if (text_buffer)
155
0
  {
156
0
    if (res && buffer->successful
157
0
      && text_buffer->successful
158
0
      && !buffer->verify (text_buffer,
159
0
        font,
160
0
        features,
161
0
        num_features,
162
0
        shaper_list))
163
0
      res = false;
164
0
    hb_buffer_destroy (text_buffer);
165
0
  }
166
167
118k
  buffer->leave ();
168
169
118k
  return res;
170
118k
}
171
172
/**
173
 * hb_shape:
174
 * @font: an #hb_font_t to use for shaping
175
 * @buffer: an #hb_buffer_t to shape
176
 * @features: (array length=num_features) (nullable): an array of user
177
 *    specified #hb_feature_t or `NULL`
178
 * @num_features: the length of @features array
179
 *
180
 * Shapes @buffer using @font turning its Unicode characters content to
181
 * positioned glyphs. If @features is not `NULL`, it will be used to control the
182
 * features applied during shaping. If two @features have the same tag but
183
 * overlapping ranges the value of the feature with the higher index takes
184
 * precedence.
185
 *
186
 * Since: 0.9.2
187
 **/
188
void
189
hb_shape (hb_font_t           *font,
190
    hb_buffer_t         *buffer,
191
    const hb_feature_t  *features,
192
    unsigned int         num_features)
193
118k
{
194
118k
  hb_shape_full (font, buffer, features, num_features, nullptr);
195
118k
}
196
197
198
#ifdef HB_EXPERIMENTAL_API
199
#ifndef HB_NO_VAR
200
201
static float
202
buffer_advance (hb_buffer_t *buffer)
203
{
204
  float a = 0;
205
  auto *pos = buffer->pos;
206
  unsigned count = buffer->len;
207
  if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
208
    for (unsigned i = 0; i < count; i++)
209
      a += pos[i].x_advance;
210
  else
211
    for (unsigned i = 0; i < count; i++)
212
      a += pos[i].y_advance;
213
  return a;
214
}
215
216
static void
217
reset_buffer (hb_buffer_t *buffer,
218
        hb_array_t<const hb_glyph_info_t> text)
219
{
220
  assert (buffer->ensure (text.length));
221
  buffer->have_positions = false;
222
  buffer->len = text.length;
223
  hb_memcpy (buffer->info, text.arrayZ, text.length * sizeof (buffer->info[0]));
224
  hb_buffer_set_content_type (buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
225
}
226
227
/**
228
 * hb_shape_justify:
229
 * @font: a mutable #hb_font_t to use for shaping
230
 * @buffer: an #hb_buffer_t to shape
231
 * @features: (array length=num_features) (nullable): an array of user
232
 *    specified #hb_feature_t or `NULL`
233
 * @num_features: the length of @features array
234
 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated
235
 *    array of shapers to use or `NULL`
236
 * @min_target_advance: Minimum advance width/height to aim for.
237
 * @max_target_advance: Maximum advance width/height to aim for.
238
 * @advance: (inout): Input/output advance width/height of the buffer.
239
 * @var_tag: (out): Variation-axis tag used for justification.
240
 * @var_value: (out): Variation-axis value used to reach target justification.
241
 *
242
 * See hb_shape_full() for basic details. If @shaper_list is not `NULL`, the specified
243
 * shapers will be used in the given order, otherwise the default shapers list
244
 * will be used.
245
 *
246
 * In addition, justify the shaping results such that the shaping results reach
247
 * the target advance width/height, depending on the buffer direction.
248
 *
249
 * If the advance of the buffer shaped with hb_shape_full() is already known,
250
 * put that in *advance. Otherwise set *advance to zero.
251
 *
252
 * This API is currently experimental and will probably change in the future.
253
 *
254
 * Return value: false if all shapers failed, true otherwise
255
 *
256
 * XSince: EXPERIMENTAL
257
 **/
258
hb_bool_t
259
hb_shape_justify (hb_font_t          *font,
260
      hb_buffer_t        *buffer,
261
      const hb_feature_t *features,
262
      unsigned int        num_features,
263
      const char * const *shaper_list,
264
      float               min_target_advance,
265
      float               max_target_advance,
266
      float              *advance, /* IN/OUT */
267
      hb_tag_t           *var_tag, /* OUT */
268
      float              *var_value /* OUT */)
269
{
270
  // TODO Negative font scales?
271
272
  /* If default advance already matches target, nothing to do. Shape and return. */
273
  if (min_target_advance <= *advance && *advance <= max_target_advance)
274
  {
275
    *var_tag = HB_TAG_NONE;
276
    *var_value = 0.0f;
277
    return hb_shape_full (font, buffer,
278
        features, num_features,
279
        shaper_list);
280
  }
281
282
  hb_face_t *face = font->face;
283
284
  /* Choose variation tag to use for justification. */
285
286
  hb_tag_t tag = HB_TAG_NONE;
287
  hb_ot_var_axis_info_t axis_info;
288
289
  hb_tag_t tags[] =
290
  {
291
    HB_TAG ('j','s','t','f'),
292
    HB_TAG ('w','d','t','h'),
293
  };
294
  for (unsigned i = 0; i < ARRAY_LENGTH (tags); i++)
295
    if (hb_ot_var_find_axis_info (face, tags[i], &axis_info))
296
    {
297
      tag = *var_tag = tags[i];
298
      break;
299
    }
300
301
  /* If no suitable variation axis found, can't justify.  Just shape and return. */
302
  if (!tag)
303
  {
304
    *var_tag = HB_TAG_NONE;
305
    *var_value = 0.0f;
306
    if (hb_shape_full (font, buffer,
307
           features, num_features,
308
           shaper_list))
309
    {
310
      *advance = buffer_advance (buffer);
311
      return true;
312
    }
313
    else
314
      return false;
315
  }
316
317
  /* Copy buffer text as we need it so we can shape multiple times. */
318
  unsigned text_len = buffer->len;
319
  auto *text_info = (hb_glyph_info_t *) hb_malloc (text_len * sizeof (buffer->info[0]));
320
  if (unlikely (text_len && !text_info))
321
    return false;
322
  hb_memcpy (text_info, buffer->info, text_len * sizeof (buffer->info[0]));
323
  auto text = hb_array<const hb_glyph_info_t> (text_info, text_len);
324
325
  /* If default advance was not provided to us, calculate it. */
326
  if (!*advance)
327
  {
328
    hb_font_set_variation (font, tag, axis_info.default_value);
329
    if (!hb_shape_full (font, buffer,
330
      features, num_features,
331
      shaper_list))
332
      return false;
333
    *advance = buffer_advance (buffer);
334
  }
335
336
  /* If default advance already matches target, nothing to do. Shape and return.
337
   * Do this again, in case advance was just calculated.
338
   */
339
  if (min_target_advance <= *advance && *advance <= max_target_advance)
340
  {
341
    *var_tag = HB_TAG_NONE;
342
    *var_value = 0.0f;
343
    return true;
344
  }
345
346
  /* Prepare for running the solver. */
347
  double a, b, ya, yb;
348
  if (*advance < min_target_advance)
349
  {
350
    /* Need to expand. */
351
    ya = (double) *advance;
352
    a = (double) axis_info.default_value;
353
    b = (double) axis_info.max_value;
354
355
    /* Shape buffer for maximum expansion to use as other
356
     * starting point for the solver. */
357
    hb_font_set_variation (font, tag, (float) b);
358
    reset_buffer (buffer, text);
359
    if (!hb_shape_full (font, buffer,
360
      features, num_features,
361
      shaper_list))
362
      return false;
363
    yb = (double) buffer_advance (buffer);
364
    /* If the maximum expansion is less than max target,
365
     * there's nothing to solve for. Just return it. */
366
    if (yb <= (double) max_target_advance)
367
    {
368
      *var_value = (float) b;
369
      *advance = (float) yb;
370
      return true;
371
    }
372
  }
373
  else
374
  {
375
    /* Need to shrink. */
376
    yb = (double) *advance;
377
    a = (double) axis_info.min_value;
378
    b = (double) axis_info.default_value;
379
380
    /* Shape buffer for maximum shrinkate to use as other
381
     * starting point for the solver. */
382
    hb_font_set_variation (font, tag, (float) a);
383
    reset_buffer (buffer, text);
384
    if (!hb_shape_full (font, buffer,
385
      features, num_features,
386
      shaper_list))
387
      return false;
388
    ya = (double) buffer_advance (buffer);
389
    /* If the maximum shrinkate is more than min target,
390
     * there's nothing to solve for. Just return it. */
391
    if (ya >= (double) min_target_advance)
392
    {
393
      *var_value = (float) a;
394
      *advance = (float) ya;
395
      return true;
396
    }
397
  }
398
399
  /* Run the solver to find a var axis value that hits
400
   * the desired width. */
401
402
  double epsilon = (b - a) / (1<<14);
403
  bool failed = false;
404
405
  auto f = [&] (double x)
406
  {
407
    hb_font_set_variation (font, tag, (float) x);
408
    reset_buffer (buffer, text);
409
    if (unlikely (!hb_shape_full (font, buffer,
410
          features, num_features,
411
          shaper_list)))
412
    {
413
      failed = true;
414
      return (double) min_target_advance;
415
    }
416
417
    double w = (double) buffer_advance (buffer);
418
    DEBUG_MSG (JUSTIFY, nullptr, "Trying '%c%c%c%c' axis parameter %f. Advance %g. Target: min %g max %g",
419
         HB_UNTAG (tag), x, w,
420
         (double) min_target_advance, (double) max_target_advance);
421
    return w;
422
  };
423
424
  double y = 0;
425
  double itp = solve_itp (f,
426
        a, b,
427
        epsilon,
428
        (double) min_target_advance, (double) max_target_advance,
429
        ya, yb, y);
430
431
  hb_free (text_info);
432
433
  if (failed)
434
    return false;
435
436
  *var_value = (float) itp;
437
  *advance = (float) y;
438
439
  return true;
440
}
441
#endif
442
#endif
443
444
445
#endif