Coverage Report

Created: 2026-10-14 09:33

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
32.6k
{
164
32.6k
  unsigned num_tables = hb_face_get_table_tags (plan->source, 0, nullptr, nullptr);
165
32.6k
  if (num_tables)
166
32.6k
    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
32.6k
}
193
194
195
static bool
196
_is_table_present (hb_face_t *source, hb_tag_t tag)
197
32.6k
{
198
199
32.6k
  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
32.6k
  hb_tag_t table_tags[32];
207
32.6k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
208
48.9k
  while (((void) hb_face_get_table_tags (source, offset, &num_tables, table_tags), num_tables))
209
32.6k
  {
210
489k
    for (unsigned i = 0; i < num_tables; ++i)
211
473k
      if (table_tags[i] == tag)
212
16.3k
  return true;
213
16.3k
    offset += num_tables;
214
16.3k
  }
215
16.3k
  return false;
216
32.6k
}
217
218
static bool
219
_should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag)
220
326k
{
221
326k
  if (plan->drop_tables.has (tag))
222
97.8k
    return true;
223
224
228k
  switch (tag)
225
228k
  {
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
16.3k
  case HB_TAG('c','v','t',' '): /* hint table, fallthrough */
230
32.6k
  case HB_TAG('f','p','g','m'): /* hint table, fallthrough */
231
48.9k
  case HB_TAG('p','r','e','p'): /* hint table, fallthrough */
232
48.9k
  case HB_TAG('h','d','m','x'): /* hint table, fallthrough */
233
48.9k
  case HB_TAG('V','D','M','X'): /* hint table, fallthrough */
234
48.9k
    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
#ifndef HB_NO_BEYOND_64K
252
  case HB_TAG('G','V','A','R'):
253
#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
179k
  default:
260
179k
    return false;
261
228k
  }
262
228k
}
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
212k
{
269
212k
  bool outline_pending = pending_subset_tags.has (HB_TAG('g','l','y','f'));
270
#ifndef HB_NO_BEYOND_64K
271
  outline_pending |= pending_subset_tags.has (HB_TAG('G','L','Y','F'));
272
#endif
273
274
212k
  switch (tag)
275
212k
  {
276
16.3k
  case HB_TAG('h','m','t','x'):
277
16.3k
  case HB_TAG('v','m','t','x'):
278
32.6k
  case HB_TAG('m','a','x','p'):
279
#ifndef HB_NO_BEYOND_64K
280
  case HB_TAG('H','M','T','X'):
281
  case HB_TAG('V','M','T','X'):
282
  case HB_TAG('M','A','X','P'):
283
#endif
284
48.9k
  case HB_TAG('O','S','/','2'):
285
48.9k
    return !plan->normalized_coords || !outline_pending;
286
0
  case HB_TAG('G','P','O','S'):
287
0
    return plan->all_axes_pinned || !pending_subset_tags.has (HB_TAG('G','D','E','F'));
288
  /* For avar2, variation tables must wait for avar to be subsetted first. */
289
0
  case HB_TAG('g','v','a','r'):
290
#ifndef HB_NO_BEYOND_64K
291
  case HB_TAG('G','V','A','R'):
292
#endif
293
0
  case HB_TAG('c','v','a','r'):
294
0
  case HB_TAG('H','V','A','R'):
295
0
  case HB_TAG('V','V','A','R'):
296
0
  case HB_TAG('M','V','A','R'):
297
0
  case HB_TAG('G','D','E','F'):
298
0
  case HB_TAG('C','F','F','2'):
299
0
    return !plan->has_avar2 || !pending_subset_tags.has (HB_TAG('a','v','a','r'));
300
163k
  default:
301
163k
    return true;
302
212k
  }
303
212k
}
304
305
static bool
306
_subset_table (hb_subset_plan_t *plan,
307
         hb_vector_t<char> &buf,
308
         hb_tag_t tag)
309
212k
{
310
212k
  if (plan->no_subset_tables.has (tag)) {
311
32.6k
    return _hb_subset_table_passthrough (plan, tag);
312
32.6k
  }
313
314
179k
  DEBUG_MSG (SUBSET, nullptr, "subset %c%c%c%c", HB_UNTAG (tag));
315
316
179k
  bool success;
317
179k
  if (_hb_subset_table_layout (plan, buf, tag, &success) ||
318
179k
      _hb_subset_table_var (plan, buf, tag, &success) ||
319
179k
      _hb_subset_table_cff (plan, buf, tag, &success) ||
320
179k
      _hb_subset_table_color (plan, buf, tag, &success) ||
321
179k
      _hb_subset_table_other (plan, buf, tag, &success))
322
130k
    return success;
323
324
325
48.9k
  switch (tag)
326
48.9k
  {
327
16.3k
  case HB_TAG('h','e','a','d'):
328
16.3k
  {
329
16.3k
    bool outline_table_handles_head =
330
16.3k
  _is_table_present (plan->source, HB_TAG('g','l','y','f')) &&
331
16.3k
  !_should_drop_table (plan, HB_TAG('g','l','y','f'));
332
#ifndef HB_NO_BEYOND_64K
333
    outline_table_handles_head |=
334
  _is_table_present (plan->source, HB_TAG('G','L','Y','F')) &&
335
  !_should_drop_table (plan, HB_TAG('G','L','Y','F'));
336
#endif
337
16.3k
    if (outline_table_handles_head)
338
16.3k
      return true; /* skip head, handled by the outline table */
339
0
    return _hb_subset_table<const OT::head> (plan, buf);
340
16.3k
  }
341
342
0
  case HB_TAG('S','T','A','T'):
343
0
    if (!plan->user_axes_location.is_empty ()) return _hb_subset_table<const OT::STAT> (plan, buf);
344
0
    else return _hb_subset_table_passthrough (plan, tag);
345
346
16.3k
  case HB_TAG('c','v','t',' '):
347
16.3k
#ifndef HB_NO_VAR
348
16.3k
    if (_is_table_present (plan->source, HB_TAG('c','v','a','r')) &&
349
0
        plan->normalized_coords && !plan->pinned_at_default)
350
0
    {
351
0
      auto &cvar = *plan->source->table.cvar;
352
0
      return OT::cvar::add_cvt_and_apply_deltas (plan, cvar.get_tuple_var_data (), &cvar);
353
0
    }
354
16.3k
#endif
355
16.3k
    return _hb_subset_table_passthrough (plan, tag);
356
48.9k
  }
357
358
16.3k
  if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED)
359
0
    return _hb_subset_table_passthrough (plan, tag);
360
361
  // Drop table
362
16.3k
  return true;
363
16.3k
}
364
365
static void _attach_accelerator_data (hb_subset_plan_t* plan,
366
                                      hb_face_t* face /* IN/OUT */)
367
0
{
368
0
  if (!plan->inprogress_accelerator) return;
369
370
  // Transfer the accelerator from the plan to us.
371
0
  hb_subset_accelerator_t* accel = plan->inprogress_accelerator;
372
0
  plan->inprogress_accelerator = nullptr;
373
374
0
  if (accel->in_error ())
375
0
  {
376
0
    hb_subset_accelerator_t::destroy (accel);
377
0
    return;
378
0
  }
379
380
  // Populate caches that need access to the final tables.
381
0
  hb_blob_ptr_t<OT::cmap> cmap_ptr (hb_sanitize_context_t ().reference_table<OT::cmap> (face));
382
0
  accel->cmap_cache = OT::cmap::create_filled_cache (cmap_ptr);
383
0
  accel->destroy_cmap_cache = OT::SubtableUnicodesCache::destroy;
384
385
0
  if (!hb_face_set_user_data(face,
386
0
                             hb_subset_accelerator_t::user_data_key(),
387
0
                             accel,
388
0
                             hb_subset_accelerator_t::destroy,
389
0
                             true))
390
0
    hb_subset_accelerator_t::destroy (accel);
391
0
}
392
393
/**
394
 * hb_subset_or_fail:
395
 * @source: font face data to be subset.
396
 * @input: input to use for the subsetting.
397
 *
398
 * Subsets a font according to provided input. Returns nullptr
399
 * if the subset operation fails or the face has no glyphs.
400
 *
401
 * Since: 2.9.0
402
 **/
403
hb_face_t *
404
hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input)
405
16.3k
{
406
16.3k
  if (unlikely (!input || !source)) return nullptr;
407
408
16.3k
  if (unlikely (!source->get_num_glyphs ()))
409
0
  {
410
0
    DEBUG_MSG (SUBSET, nullptr, "No glyphs in source font.");
411
0
    return nullptr;
412
0
  }
413
414
16.3k
  hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (source, input);
415
16.3k
  if (unlikely (!plan)) {
416
0
    return nullptr;
417
0
  }
418
419
16.3k
  hb_face_t * result = hb_subset_plan_execute_or_fail (plan);
420
16.3k
  hb_subset_plan_destroy (plan);
421
16.3k
  return result;
422
16.3k
}
423
424
425
/**
426
 * hb_subset_plan_execute_or_fail:
427
 * @plan: a subsetting plan.
428
 *
429
 * Executes the provided subsetting @plan.
430
 *
431
 * Return value:
432
 * on success returns a reference to generated font subset. If the subsetting operation fails
433
 * returns nullptr.
434
 *
435
 * Since: 4.0.0
436
 **/
437
hb_face_t *
438
hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan)
439
16.3k
{
440
16.3k
  if (unlikely (!plan || plan->in_error ())) {
441
0
    return nullptr;
442
0
  }
443
444
16.3k
  hb_tag_t table_tags[32];
445
16.3k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
446
447
16.3k
  hb_set_t subsetted_tags, pending_subset_tags;
448
32.6k
  while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables))
449
16.3k
  {
450
326k
    for (unsigned i = 0; i < num_tables; ++i)
451
309k
    {
452
309k
      hb_tag_t tag = table_tags[i];
453
309k
      if (_should_drop_table (plan, tag)) continue;
454
212k
      pending_subset_tags.add (tag);
455
212k
    }
456
457
16.3k
    offset += num_tables;
458
16.3k
  }
459
460
16.3k
  bool success = true;
461
462
16.3k
  {
463
    // Grouping to deallocate buf before calling hb_face_reference (plan->dest).
464
465
16.3k
    hb_vector_t<char> buf;
466
16.3k
    buf.alloc (8192 - 16);
467
468
32.6k
    while (!pending_subset_tags.is_empty ())
469
16.3k
    {
470
16.3k
      if (subsetted_tags.in_error ()
471
16.3k
    || pending_subset_tags.in_error ()) {
472
0
  success = false;
473
0
  goto end;
474
0
      }
475
476
16.3k
      bool made_changes = false;
477
16.3k
      for (hb_tag_t tag : pending_subset_tags)
478
212k
      {
479
212k
  if (!_dependencies_satisfied (plan, tag,
480
212k
              subsetted_tags,
481
212k
              pending_subset_tags))
482
0
  {
483
    // delayed subsetting for some tables since they might have dependency on other tables
484
    // in some cases: e.g: during instantiating glyf tables, hmetrics/vmetrics are updated
485
    // and saved in subset plan, hmtx/vmtx subsetting need to use these updated metrics values
486
0
    continue;
487
0
  }
488
489
212k
  pending_subset_tags.del (tag);
490
212k
  subsetted_tags.add (tag);
491
212k
  made_changes = true;
492
493
212k
  success = _subset_table (plan, buf, tag);
494
212k
  if (unlikely (!success)) goto end;
495
212k
      }
496
497
16.3k
      if (!made_changes)
498
0
      {
499
0
  DEBUG_MSG (SUBSET, nullptr, "Table dependencies unable to be satisfied. Subset failed.");
500
0
  success = false;
501
0
  goto end;
502
0
      }
503
16.3k
    }
504
16.3k
  }
505
506
16.3k
  if (success && plan->attach_accelerator_data) {
507
0
    _attach_accelerator_data (plan, plan->dest);
508
0
  }
509
510
16.3k
end:
511
16.3k
  return success ? hb_face_reference (plan->dest) : nullptr;
512
16.3k
}