Coverage Report

Created: 2023-09-25 06:24

/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): an array of
95
 *    constant strings
96
 *
97
 * Since: 0.9.2
98
 **/
99
const char **
100
hb_shape_list_shapers ()
101
0
{
102
0
  return static_shaper_list.get_unconst ();
103
0
}
104
105
106
/**
107
 * hb_shape_full:
108
 * @font: an #hb_font_t to use for shaping
109
 * @buffer: an #hb_buffer_t to shape
110
 * @features: (array length=num_features) (nullable): an array of user
111
 *    specified #hb_feature_t or `NULL`
112
 * @num_features: the length of @features array
113
 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated
114
 *    array of shapers to use or `NULL`
115
 *
116
 * See hb_shape() for details. If @shaper_list is not `NULL`, the specified
117
 * shapers will be used in the given order, otherwise the default shapers list
118
 * will be used.
119
 *
120
 * Return value: false if all shapers failed, true otherwise
121
 *
122
 * Since: 0.9.2
123
 **/
124
hb_bool_t
125
hb_shape_full (hb_font_t          *font,
126
         hb_buffer_t        *buffer,
127
         const hb_feature_t *features,
128
         unsigned int        num_features,
129
         const char * const *shaper_list)
130
204k
{
131
204k
  if (unlikely (!buffer->len))
132
0
    return true;
133
134
204k
  buffer->enter ();
135
136
204k
  hb_buffer_t *text_buffer = nullptr;
137
204k
  if (buffer->flags & HB_BUFFER_FLAG_VERIFY)
138
0
  {
139
0
    text_buffer = hb_buffer_create ();
140
0
    hb_buffer_append (text_buffer, buffer, 0, -1);
141
0
  }
142
143
204k
  hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached2 (font->face, &buffer->props,
144
204k
                    features, num_features,
145
204k
                    font->coords, font->num_coords,
146
204k
                    shaper_list);
147
148
204k
  hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features);
149
150
204k
  if (buffer->max_ops <= 0)
151
0
    buffer->shaping_failed = true;
152
153
204k
  hb_shape_plan_destroy (shape_plan);
154
155
204k
  if (text_buffer)
156
0
  {
157
0
    if (res && buffer->successful && !buffer->shaping_failed
158
0
      && text_buffer->successful
159
0
      && !buffer->verify (text_buffer,
160
0
        font,
161
0
        features,
162
0
        num_features,
163
0
        shaper_list))
164
0
      res = false;
165
0
    hb_buffer_destroy (text_buffer);
166
0
  }
167
168
204k
  buffer->leave ();
169
170
204k
  return res;
171
204k
}
172
173
/**
174
 * hb_shape:
175
 * @font: an #hb_font_t to use for shaping
176
 * @buffer: an #hb_buffer_t to shape
177
 * @features: (array length=num_features) (nullable): an array of user
178
 *    specified #hb_feature_t or `NULL`
179
 * @num_features: the length of @features array
180
 *
181
 * Shapes @buffer using @font turning its Unicode characters content to
182
 * positioned glyphs. If @features is not `NULL`, it will be used to control the
183
 * features applied during shaping. If two @features have the same tag but
184
 * overlapping ranges the value of the feature with the higher index takes
185
 * precedence.
186
 *
187
 * Since: 0.9.2
188
 **/
189
void
190
hb_shape (hb_font_t           *font,
191
    hb_buffer_t         *buffer,
192
    const hb_feature_t  *features,
193
    unsigned int         num_features)
194
204k
{
195
204k
  hb_shape_full (font, buffer, features, num_features, nullptr);
196
204k
}
197
198
199
#ifdef HB_EXPERIMENTAL_API
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
442
#endif
443
444
445
#endif