Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset.cc
Line
Count
Source
1
/*
2
 * Copyright © 2018  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): Garret Rieger, Rod Sheeter, Behdad Esfahbod
25
 */
26
27
#include "hb.hh"
28
29
#include "hb-open-type.hh"
30
#include "hb-open-file.hh"
31
32
#include "hb-subset.hh"
33
#include "hb-subset-table.hh"
34
#include "hb-subset-accelerator.hh"
35
36
#include "hb-ot-cmap-table.hh"
37
#include "hb-ot-var-cvar-table.hh"
38
#include "hb-ot-head-table.hh"
39
#include "hb-ot-stat-table.hh"
40
#include "hb-ot-post-table-v2subset.hh"
41
42
43
/**
44
 * SECTION:hb-subset
45
 * @title: hb-subset
46
 * @short_description: Subsets font files.
47
 * @include: hb-subset.h
48
 *
49
 * Subsetting reduces the codepoint coverage of font files and removes all data
50
 * that is no longer needed. A subset input describes the desired subset. The input is
51
 * provided along with a font to the subsetting operation. Output is a new font file
52
 * containing only the data specified in the input.
53
 *
54
 * Currently most outline and bitmap tables are supported: glyf, CFF, CFF2, sbix,
55
 * COLR, and CBDT/CBLC. This also includes fonts with variable outlines via OpenType
56
 * variations. Notably EBDT/EBLC and SVG are not supported. Layout subsetting is supported
57
 * only for OpenType Layout tables (GSUB, GPOS, GDEF). Notably subsetting of graphite or AAT tables
58
 * is not yet supported.
59
 *
60
 * Fonts with graphite or AAT tables may still be subsetted but will likely need to use the
61
 * retain glyph ids option and configure the subset to pass through the layout tables untouched.
62
 */
63
64
65
hb_user_data_key_t _hb_subset_accelerator_user_data_key = {};
66
67
68
/*
69
 * The list of tables in the open type spec. Used to check for tables that may need handling
70
 * if we are unable to list the tables in a face.
71
 */
72
static hb_tag_t known_tables[] {
73
  HB_TAG('a','v','a','r'),
74
  HB_TAG('B','A','S','E'),
75
  HB_TAG('C','B','D','T'),
76
  HB_TAG('C','B','L','C'),
77
  HB_TAG('C','F','F',' '),
78
  HB_TAG('C','F','F','2'),
79
  HB_TAG('c','m','a','p'),
80
  HB_TAG('C','O','L','R'),
81
  HB_TAG('C','P','A','L'),
82
  HB_TAG('c','v','a','r'),
83
  HB_TAG('c','v','t',' '),
84
  HB_TAG('D','S','I','G'),
85
  HB_TAG('E','B','D','T'),
86
  HB_TAG('E','B','L','C'),
87
  HB_TAG('E','B','S','C'),
88
  HB_TAG('f','p','g','m'),
89
  HB_TAG('f','v','a','r'),
90
  HB_TAG('g','a','s','p'),
91
  HB_TAG('G','D','E','F'),
92
  HB_TAG('g','l','y','f'),
93
#ifndef HB_NO_BEYOND_64K
94
  HB_TAG('G','L','Y','F'),
95
#endif
96
  HB_TAG('G','P','O','S'),
97
  HB_TAG('G','S','U','B'),
98
  HB_TAG('g','v','a','r'),
99
#ifndef HB_NO_BEYOND_64K
100
  HB_TAG('G','V','A','R'),
101
#endif
102
  HB_TAG('h','d','m','x'),
103
  HB_TAG('h','e','a','d'),
104
  HB_TAG('h','h','e','a'),
105
#ifndef HB_NO_BEYOND_64K
106
  HB_TAG('H','H','E','A'),
107
#endif
108
  HB_TAG('h','m','t','x'),
109
#ifndef HB_NO_BEYOND_64K
110
  HB_TAG('H','M','T','X'),
111
#endif
112
  HB_TAG('H','V','A','R'),
113
  HB_TAG('J','S','T','F'),
114
  HB_TAG('k','e','r','n'),
115
  HB_TAG('l','o','c','a'),
116
#ifndef HB_NO_BEYOND_64K
117
  HB_TAG('L','O','C','A'),
118
#endif
119
  HB_TAG('L','T','S','H'),
120
  HB_TAG('M','A','T','H'),
121
  HB_TAG('m','a','x','p'),
122
#ifndef HB_NO_BEYOND_64K
123
  HB_TAG('M','A','X','P'),
124
#endif
125
  HB_TAG('M','E','R','G'),
126
  HB_TAG('m','e','t','a'),
127
  HB_TAG('M','V','A','R'),
128
  HB_TAG('P','C','L','T'),
129
  HB_TAG('p','o','s','t'),
130
  HB_TAG('p','r','e','p'),
131
  HB_TAG('s','b','i','x'),
132
  HB_TAG('S','T','A','T'),
133
  HB_TAG('S','V','G',' '),
134
  HB_TAG('V','D','M','X'),
135
  HB_TAG('v','h','e','a'),
136
#ifndef HB_NO_BEYOND_64K
137
  HB_TAG('V','H','E','A'),
138
#endif
139
  HB_TAG('v','m','t','x'),
140
#ifndef HB_NO_BEYOND_64K
141
  HB_TAG('V','M','T','X'),
142
#endif
143
  HB_TAG('V','O','R','G'),
144
  HB_TAG('V','A','R','C'),
145
  HB_TAG('V','V','A','R'),
146
  HB_TAG('n','a','m','e'),
147
  HB_TAG('O','S','/','2')
148
};
149
150
static bool _table_is_empty (const hb_face_t *face, hb_tag_t tag)
151
0
{
152
0
  hb_blob_t* blob = hb_face_reference_table (face, tag);
153
0
  bool result = (blob == hb_blob_get_empty ());
154
0
  hb_blob_destroy (blob);
155
0
  return result;
156
0
}
157
158
static unsigned int
159
_get_table_tags (const hb_subset_plan_t* plan,
160
                 unsigned int  start_offset,
161
                 unsigned int *table_count, /* IN/OUT */
162
                 hb_tag_t     *table_tags /* OUT */)
163
40.8k
{
164
40.8k
  unsigned num_tables = hb_face_get_table_tags (plan->source, 0, nullptr, nullptr);
165
40.8k
  if (num_tables)
166
40.8k
    return hb_face_get_table_tags (plan->source, start_offset, table_count, table_tags);
167
168
  // If face has 0 tables associated with it, assume that it was built from
169
  // hb_face_create_tables and thus is unable to list its tables. Fallback to
170
  // checking each table type we can handle for existence instead.
171
0
  auto it =
172
0
      hb_concat (
173
0
          + hb_array (known_tables)
174
0
          | hb_filter ([&] (hb_tag_t tag) {
175
0
            return !_table_is_empty (plan->source, tag) && !plan->no_subset_tables.has (tag);
176
0
          })
177
0
          | hb_map ([] (hb_tag_t tag) -> hb_tag_t { return tag; }),
178
179
0
          plan->no_subset_tables.iter ()
180
0
          | hb_filter([&] (hb_tag_t tag) {
181
0
            return !_table_is_empty (plan->source, tag);
182
0
          }));
183
184
0
  it += start_offset;
185
186
0
  unsigned num_written = 0;
187
0
  while (bool (it) && num_written < *table_count)
188
0
    table_tags[num_written++] = *it++;
189
190
0
  *table_count = num_written;
191
0
  return num_written;
192
40.8k
}
193
194
195
static bool
196
_is_table_present (hb_face_t *source, hb_tag_t tag)
197
44.2k
{
198
199
44.2k
  if (!hb_face_get_table_tags (source, 0, nullptr, nullptr)) {
200
    // If face has 0 tables associated with it, assume that it was built from
201
    // hb_face_create_tables and thus is unable to list its tables. Fallback to
202
    // checking if the blob associated with tag is empty.
203
0
    return !_table_is_empty (source, tag);
204
0
  }
205
206
44.2k
  hb_tag_t table_tags[32];
207
44.2k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
208
75.1k
  while (((void) hb_face_get_table_tags (source, offset, &num_tables, table_tags), num_tables))
209
44.4k
  {
210
690k
    for (unsigned i = 0; i < num_tables; ++i)
211
660k
      if (table_tags[i] == tag)
212
13.5k
  return true;
213
30.8k
    offset += num_tables;
214
30.8k
  }
215
30.7k
  return false;
216
44.2k
}
217
218
static bool
219
_should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag)
220
343k
{
221
343k
  if (plan->drop_tables.has (tag))
222
110k
    return true;
223
224
233k
  switch (tag)
225
233k
  {
226
0
  case HB_TAG('c','v','a','r'): /* hint table, fallthrough */
227
0
    return plan->all_axes_pinned || (plan->flags & HB_SUBSET_FLAGS_NO_HINTING);
228
229
12.4k
  case HB_TAG('c','v','t',' '): /* hint table, fallthrough */
230
23.8k
  case HB_TAG('f','p','g','m'): /* hint table, fallthrough */
231
35.2k
  case HB_TAG('p','r','e','p'): /* hint table, fallthrough */
232
35.2k
  case HB_TAG('h','d','m','x'): /* hint table, fallthrough */
233
35.2k
  case HB_TAG('V','D','M','X'): /* hint table, fallthrough */
234
35.2k
    return plan->flags & HB_SUBSET_FLAGS_NO_HINTING;
235
236
#ifdef HB_NO_SUBSET_LAYOUT
237
    // Drop Layout Tables if requested.
238
  case HB_TAG('G','D','E','F'):
239
  case HB_TAG('G','P','O','S'):
240
  case HB_TAG('G','S','U','B'):
241
  case HB_TAG('m','o','r','x'):
242
  case HB_TAG('m','o','r','t'):
243
  case HB_TAG('k','e','r','x'):
244
  case HB_TAG('k','e','r','n'):
245
    return true;
246
#endif
247
248
0
  case HB_TAG('a','v','a','r'):
249
0
  case HB_TAG('f','v','a','r'):
250
0
  case HB_TAG('g','v','a','r'):
251
0
#ifndef HB_NO_BEYOND_64K
252
0
  case HB_TAG('G','V','A','R'):
253
0
#endif
254
0
  case HB_TAG('H','V','A','R'):
255
0
  case HB_TAG('V','V','A','R'):
256
0
  case HB_TAG('M','V','A','R'):
257
0
    return plan->all_axes_pinned;
258
259
198k
  default:
260
198k
    return false;
261
233k
  }
262
233k
}
263
264
static bool
265
_dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag,
266
                         const hb_set_t &subsetted_tags,
267
                         const hb_set_t &pending_subset_tags)
268
181k
{
269
181k
  bool outline_pending = pending_subset_tags.has (HB_TAG('g','l','y','f'));
270
181k
#ifndef HB_NO_BEYOND_64K
271
181k
  outline_pending |= pending_subset_tags.has (HB_TAG('G','L','Y','F'));
272
181k
#endif
273
274
181k
  switch (tag)
275
181k
  {
276
14.6k
  case HB_TAG('h','m','t','x'):
277
14.6k
  case HB_TAG('v','m','t','x'):
278
29.3k
  case HB_TAG('m','a','x','p'):
279
29.3k
#ifndef HB_NO_BEYOND_64K
280
29.3k
  case HB_TAG('H','M','T','X'):
281
29.3k
  case HB_TAG('V','M','T','X'):
282
29.3k
  case HB_TAG('M','A','X','P'):
283
29.3k
#endif
284
46.1k
  case HB_TAG('O','S','/','2'):
285
46.1k
    return !plan->normalized_coords || !outline_pending;
286
0
  case HB_TAG('G','P','O','S'):
287
0
  case HB_TAG('G','S','U','B'):
288
0
    return plan->all_axes_pinned || !pending_subset_tags.has (HB_TAG('G','D','E','F'));
289
  /* For avar2, variation tables must wait for avar to be subsetted first. */
290
0
  case HB_TAG('g','v','a','r'):
291
0
#ifndef HB_NO_BEYOND_64K
292
0
  case HB_TAG('G','V','A','R'):
293
0
#endif
294
0
  case HB_TAG('c','v','a','r'):
295
0
  case HB_TAG('H','V','A','R'):
296
0
  case HB_TAG('V','V','A','R'):
297
0
  case HB_TAG('M','V','A','R'):
298
0
  case HB_TAG('G','D','E','F'):
299
0
  case HB_TAG('C','F','F','2'):
300
0
    return !plan->has_avar2 || !pending_subset_tags.has (HB_TAG('a','v','a','r'));
301
135k
  default:
302
135k
    return true;
303
181k
  }
304
181k
}
305
306
static bool
307
_subset_table (hb_subset_plan_t *plan,
308
         hb_vector_t<char> &buf,
309
         hb_tag_t tag)
310
181k
{
311
181k
  if (plan->no_subset_tables.has (tag)) {
312
22.7k
    return _hb_subset_table_passthrough (plan, tag);
313
22.7k
  }
314
315
158k
  DEBUG_MSG (SUBSET, nullptr, "subset %c%c%c%c", HB_UNTAG (tag));
316
317
158k
  bool success;
318
158k
  if (_hb_subset_table_layout (plan, buf, tag, &success) ||
319
158k
      _hb_subset_table_var (plan, buf, tag, &success) ||
320
158k
      _hb_subset_table_cff (plan, buf, tag, &success) ||
321
158k
      _hb_subset_table_color (plan, buf, tag, &success) ||
322
158k
      _hb_subset_table_other (plan, buf, tag, &success))
323
118k
    return success;
324
325
326
40.4k
  switch (tag)
327
40.4k
  {
328
15.9k
  case HB_TAG('h','e','a','d'):
329
15.9k
  {
330
15.9k
    bool outline_table_handles_head =
331
15.9k
  _is_table_present (plan->source, HB_TAG('g','l','y','f')) &&
332
13.5k
  !_should_drop_table (plan, HB_TAG('g','l','y','f'));
333
15.9k
#ifndef HB_NO_BEYOND_64K
334
15.9k
    outline_table_handles_head |=
335
15.9k
  _is_table_present (plan->source, HB_TAG('G','L','Y','F')) &&
336
0
  !_should_drop_table (plan, HB_TAG('G','L','Y','F'));
337
15.9k
#endif
338
15.9k
    if (outline_table_handles_head)
339
13.5k
      return true; /* skip head, handled by the outline table */
340
2.39k
    return _hb_subset_table<const OT::head> (plan, buf);
341
15.9k
  }
342
343
0
  case HB_TAG('S','T','A','T'):
344
0
    if (!plan->user_axes_location.is_empty ()) return _hb_subset_table<const OT::STAT> (plan, buf);
345
0
    else return _hb_subset_table_passthrough (plan, tag);
346
347
12.4k
  case HB_TAG('c','v','t',' '):
348
12.4k
#ifndef HB_NO_VAR
349
12.4k
    if (_is_table_present (plan->source, HB_TAG('c','v','a','r')) &&
350
12
        plan->normalized_coords && !plan->pinned_at_default)
351
0
    {
352
0
      auto &cvar = *plan->source->table.cvar;
353
0
      return OT::cvar::add_cvt_and_apply_deltas (plan, cvar.get_tuple_var_data (), &cvar);
354
0
    }
355
12.4k
#endif
356
12.4k
    return _hb_subset_table_passthrough (plan, tag);
357
40.4k
  }
358
359
12.1k
  if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED)
360
0
    return _hb_subset_table_passthrough (plan, tag);
361
362
  // Drop table
363
12.1k
  return true;
364
12.1k
}
365
366
static void _attach_accelerator_data (hb_subset_plan_t* plan,
367
                                      hb_face_t* face /* IN/OUT */)
368
0
{
369
0
  if (!plan->inprogress_accelerator) return;
370
371
  // Transfer the accelerator from the plan to us.
372
0
  hb_subset_accelerator_t* accel = plan->inprogress_accelerator;
373
0
  plan->inprogress_accelerator = nullptr;
374
375
0
  if (accel->in_error ())
376
0
  {
377
0
    hb_subset_accelerator_t::destroy (accel);
378
0
    return;
379
0
  }
380
381
  // Populate caches that need access to the final tables.
382
0
  hb_blob_ptr_t<OT::cmap> cmap_ptr (hb_sanitize_context_t ().reference_table<OT::cmap> (face));
383
0
  accel->cmap_cache = OT::cmap::create_filled_cache (cmap_ptr);
384
0
  accel->destroy_cmap_cache = OT::SubtableUnicodesCache::destroy;
385
386
0
  if (!hb_face_set_user_data(face,
387
0
                             hb_subset_accelerator_t::user_data_key(),
388
0
                             accel,
389
0
                             hb_subset_accelerator_t::destroy,
390
0
                             true))
391
0
    hb_subset_accelerator_t::destroy (accel);
392
0
}
393
394
/**
395
 * hb_subset_or_fail:
396
 * @source: font face data to be subset.
397
 * @input: input to use for the subsetting.
398
 *
399
 * Subsets a font according to provided input. Returns nullptr
400
 * if the subset operation fails or the face has no glyphs.
401
 *
402
 * Since: 2.9.0
403
 **/
404
hb_face_t *
405
hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input)
406
20.3k
{
407
20.3k
  if (unlikely (!input || !source)) return nullptr;
408
409
20.3k
  if (unlikely (!source->get_num_glyphs ()))
410
0
  {
411
0
    DEBUG_MSG (SUBSET, nullptr, "No glyphs in source font.");
412
0
    return nullptr;
413
0
  }
414
415
20.3k
  hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (source, input);
416
20.3k
  if (unlikely (!plan)) {
417
0
    return nullptr;
418
0
  }
419
420
20.3k
  hb_face_t * result = hb_subset_plan_execute_or_fail (plan);
421
20.3k
  hb_subset_plan_destroy (plan);
422
20.3k
  return result;
423
20.3k
}
424
425
426
/**
427
 * hb_subset_plan_execute_or_fail:
428
 * @plan: a subsetting plan.
429
 *
430
 * Executes the provided subsetting @plan.
431
 *
432
 * Return value:
433
 * on success returns a reference to generated font subset. If the subsetting operation fails
434
 * returns nullptr.
435
 *
436
 * Since: 4.0.0
437
 **/
438
hb_face_t *
439
hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan)
440
20.3k
{
441
20.3k
  if (unlikely (!plan || plan->in_error ())) {
442
0
    return nullptr;
443
0
  }
444
445
20.3k
  hb_tag_t table_tags[32];
446
20.3k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
447
448
20.3k
  hb_set_t subsetted_tags, pending_subset_tags;
449
40.8k
  while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables))
450
20.4k
  {
451
350k
    for (unsigned i = 0; i < num_tables; ++i)
452
330k
    {
453
330k
      hb_tag_t tag = table_tags[i];
454
330k
      if (_should_drop_table (plan, tag)) continue;
455
219k
      pending_subset_tags.add (tag);
456
219k
    }
457
458
20.4k
    offset += num_tables;
459
20.4k
  }
460
461
20.3k
  bool success = true;
462
463
20.3k
  {
464
    // Grouping to deallocate buf before calling hb_face_reference (plan->dest).
465
466
20.3k
    hb_vector_t<char> buf;
467
20.3k
    buf.alloc (8192 - 16);
468
469
32.8k
    while (!pending_subset_tags.is_empty ())
470
20.3k
    {
471
20.3k
      if (subsetted_tags.in_error ()
472
20.3k
    || pending_subset_tags.in_error ()) {
473
0
  success = false;
474
0
  goto end;
475
0
      }
476
477
20.3k
      bool made_changes = false;
478
20.3k
      for (hb_tag_t tag : pending_subset_tags)
479
181k
      {
480
181k
  if (!_dependencies_satisfied (plan, tag,
481
181k
              subsetted_tags,
482
181k
              pending_subset_tags))
483
0
  {
484
    // delayed subsetting for some tables since they might have dependency on other tables
485
    // in some cases: e.g: during instantiating glyf tables, hmetrics/vmetrics are updated
486
    // and saved in subset plan, hmtx/vmtx subsetting need to use these updated metrics values
487
0
    continue;
488
0
  }
489
490
181k
  pending_subset_tags.del (tag);
491
181k
  subsetted_tags.add (tag);
492
181k
  made_changes = true;
493
494
181k
  success = _subset_table (plan, buf, tag);
495
181k
  if (unlikely (!success)) goto end;
496
181k
      }
497
498
12.5k
      if (!made_changes)
499
0
      {
500
0
  DEBUG_MSG (SUBSET, nullptr, "Table dependencies unable to be satisfied. Subset failed.");
501
0
  success = false;
502
0
  goto end;
503
0
      }
504
12.5k
    }
505
20.3k
  }
506
507
12.5k
  if (success && plan->attach_accelerator_data) {
508
0
    _attach_accelerator_data (plan, plan->dest);
509
0
  }
510
511
20.3k
end:
512
20.3k
  return success ? hb_face_reference (plan->dest) : nullptr;
513
12.5k
}