Coverage Report

Created: 2026-09-28 10:59

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
  HB_TAG('G','P','O','S'),
94
  HB_TAG('G','S','U','B'),
95
  HB_TAG('g','v','a','r'),
96
  HB_TAG('h','d','m','x'),
97
  HB_TAG('h','e','a','d'),
98
  HB_TAG('h','h','e','a'),
99
  HB_TAG('h','m','t','x'),
100
  HB_TAG('H','V','A','R'),
101
  HB_TAG('J','S','T','F'),
102
  HB_TAG('k','e','r','n'),
103
  HB_TAG('l','o','c','a'),
104
  HB_TAG('L','T','S','H'),
105
  HB_TAG('M','A','T','H'),
106
  HB_TAG('m','a','x','p'),
107
  HB_TAG('M','E','R','G'),
108
  HB_TAG('m','e','t','a'),
109
  HB_TAG('M','V','A','R'),
110
  HB_TAG('P','C','L','T'),
111
  HB_TAG('p','o','s','t'),
112
  HB_TAG('p','r','e','p'),
113
  HB_TAG('s','b','i','x'),
114
  HB_TAG('S','T','A','T'),
115
  HB_TAG('S','V','G',' '),
116
  HB_TAG('V','D','M','X'),
117
  HB_TAG('v','h','e','a'),
118
  HB_TAG('v','m','t','x'),
119
  HB_TAG('V','O','R','G'),
120
  HB_TAG('V','A','R','C'),
121
  HB_TAG('V','V','A','R'),
122
  HB_TAG('n','a','m','e'),
123
  HB_TAG('O','S','/','2')
124
};
125
126
static bool _table_is_empty (const hb_face_t *face, hb_tag_t tag)
127
0
{
128
0
  hb_blob_t* blob = hb_face_reference_table (face, tag);
129
0
  bool result = (blob == hb_blob_get_empty ());
130
0
  hb_blob_destroy (blob);
131
0
  return result;
132
0
}
133
134
static unsigned int
135
_get_table_tags (const hb_subset_plan_t* plan,
136
                 unsigned int  start_offset,
137
                 unsigned int *table_count, /* IN/OUT */
138
                 hb_tag_t     *table_tags /* OUT */)
139
31.1k
{
140
31.1k
  unsigned num_tables = hb_face_get_table_tags (plan->source, 0, nullptr, nullptr);
141
31.1k
  if (num_tables)
142
31.1k
    return hb_face_get_table_tags (plan->source, start_offset, table_count, table_tags);
143
144
  // If face has 0 tables associated with it, assume that it was built from
145
  // hb_face_create_tables and thus is unable to list its tables. Fallback to
146
  // checking each table type we can handle for existence instead.
147
0
  auto it =
148
0
      hb_concat (
149
0
          + hb_array (known_tables)
150
0
          | hb_filter ([&] (hb_tag_t tag) {
151
0
            return !_table_is_empty (plan->source, tag) && !plan->no_subset_tables.has (tag);
152
0
          })
153
0
          | hb_map ([] (hb_tag_t tag) -> hb_tag_t { return tag; }),
154
155
0
          plan->no_subset_tables.iter ()
156
0
          | hb_filter([&] (hb_tag_t tag) {
157
0
            return !_table_is_empty (plan->source, tag);
158
0
          }));
159
160
0
  it += start_offset;
161
162
0
  unsigned num_written = 0;
163
0
  while (bool (it) && num_written < *table_count)
164
0
    table_tags[num_written++] = *it++;
165
166
0
  *table_count = num_written;
167
0
  return num_written;
168
31.1k
}
169
170
171
static bool
172
_is_table_present (hb_face_t *source, hb_tag_t tag)
173
31.1k
{
174
175
31.1k
  if (!hb_face_get_table_tags (source, 0, nullptr, nullptr)) {
176
    // If face has 0 tables associated with it, assume that it was built from
177
    // hb_face_create_tables and thus is unable to list its tables. Fallback to
178
    // checking if the blob associated with tag is empty.
179
0
    return !_table_is_empty (source, tag);
180
0
  }
181
182
31.1k
  hb_tag_t table_tags[32];
183
31.1k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
184
46.7k
  while (((void) hb_face_get_table_tags (source, offset, &num_tables, table_tags), num_tables))
185
31.1k
  {
186
467k
    for (unsigned i = 0; i < num_tables; ++i)
187
451k
      if (table_tags[i] == tag)
188
15.5k
  return true;
189
15.5k
    offset += num_tables;
190
15.5k
  }
191
15.5k
  return false;
192
31.1k
}
193
194
static bool
195
_should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag)
196
311k
{
197
311k
  if (plan->drop_tables.has (tag))
198
93.4k
    return true;
199
200
218k
  switch (tag)
201
218k
  {
202
0
  case HB_TAG('c','v','a','r'): /* hint table, fallthrough */
203
0
    return plan->all_axes_pinned || (plan->flags & HB_SUBSET_FLAGS_NO_HINTING);
204
205
15.5k
  case HB_TAG('c','v','t',' '): /* hint table, fallthrough */
206
31.1k
  case HB_TAG('f','p','g','m'): /* hint table, fallthrough */
207
46.7k
  case HB_TAG('p','r','e','p'): /* hint table, fallthrough */
208
46.7k
  case HB_TAG('h','d','m','x'): /* hint table, fallthrough */
209
46.7k
  case HB_TAG('V','D','M','X'): /* hint table, fallthrough */
210
46.7k
    return plan->flags & HB_SUBSET_FLAGS_NO_HINTING;
211
212
#ifdef HB_NO_SUBSET_LAYOUT
213
    // Drop Layout Tables if requested.
214
  case HB_TAG('G','D','E','F'):
215
  case HB_TAG('G','P','O','S'):
216
  case HB_TAG('G','S','U','B'):
217
  case HB_TAG('m','o','r','x'):
218
  case HB_TAG('m','o','r','t'):
219
  case HB_TAG('k','e','r','x'):
220
  case HB_TAG('k','e','r','n'):
221
    return true;
222
#endif
223
224
0
  case HB_TAG('a','v','a','r'):
225
0
  case HB_TAG('f','v','a','r'):
226
0
  case HB_TAG('g','v','a','r'):
227
0
  case HB_TAG('H','V','A','R'):
228
0
  case HB_TAG('V','V','A','R'):
229
0
  case HB_TAG('M','V','A','R'):
230
0
    return plan->all_axes_pinned;
231
232
171k
  default:
233
171k
    return false;
234
218k
  }
235
218k
}
236
237
static bool
238
_dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag,
239
                         const hb_set_t &subsetted_tags,
240
                         const hb_set_t &pending_subset_tags)
241
202k
{
242
202k
  switch (tag)
243
202k
  {
244
15.5k
  case HB_TAG('h','m','t','x'):
245
15.5k
  case HB_TAG('v','m','t','x'):
246
31.1k
  case HB_TAG('m','a','x','p'):
247
46.7k
  case HB_TAG('O','S','/','2'):
248
46.7k
    return !plan->normalized_coords || !pending_subset_tags.has (HB_TAG('g','l','y','f'));
249
0
  case HB_TAG('G','P','O','S'):
250
0
    return plan->all_axes_pinned || !pending_subset_tags.has (HB_TAG('G','D','E','F'));
251
  /* For avar2, variation tables must wait for avar to be subsetted first. */
252
0
  case HB_TAG('g','v','a','r'):
253
0
  case HB_TAG('c','v','a','r'):
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
  case HB_TAG('G','D','E','F'):
258
0
  case HB_TAG('C','F','F','2'):
259
0
    return !plan->has_avar2 || !pending_subset_tags.has (HB_TAG('a','v','a','r'));
260
155k
  default:
261
155k
    return true;
262
202k
  }
263
202k
}
264
265
static bool
266
_subset_table (hb_subset_plan_t *plan,
267
         hb_vector_t<char> &buf,
268
         hb_tag_t tag)
269
202k
{
270
202k
  if (plan->no_subset_tables.has (tag)) {
271
31.1k
    return _hb_subset_table_passthrough (plan, tag);
272
31.1k
  }
273
274
171k
  DEBUG_MSG (SUBSET, nullptr, "subset %c%c%c%c", HB_UNTAG (tag));
275
276
171k
  bool success;
277
171k
  if (_hb_subset_table_layout (plan, buf, tag, &success) ||
278
171k
      _hb_subset_table_var (plan, buf, tag, &success) ||
279
171k
      _hb_subset_table_cff (plan, buf, tag, &success) ||
280
171k
      _hb_subset_table_color (plan, buf, tag, &success) ||
281
171k
      _hb_subset_table_other (plan, buf, tag, &success))
282
124k
    return success;
283
284
285
46.7k
  switch (tag)
286
46.7k
  {
287
15.5k
  case HB_TAG('h','e','a','d'):
288
15.5k
    if (_is_table_present (plan->source, HB_TAG('g','l','y','f')) && !_should_drop_table (plan, HB_TAG('g','l','y','f')))
289
15.5k
      return true; /* skip head, handled by glyf */
290
0
    return _hb_subset_table<const OT::head> (plan, buf);
291
292
0
  case HB_TAG('S','T','A','T'):
293
0
    if (!plan->user_axes_location.is_empty ()) return _hb_subset_table<const OT::STAT> (plan, buf);
294
0
    else return _hb_subset_table_passthrough (plan, tag);
295
296
15.5k
  case HB_TAG('c','v','t',' '):
297
15.5k
#ifndef HB_NO_VAR
298
15.5k
    if (_is_table_present (plan->source, HB_TAG('c','v','a','r')) &&
299
0
        plan->normalized_coords && !plan->pinned_at_default)
300
0
    {
301
0
      auto &cvar = *plan->source->table.cvar;
302
0
      return OT::cvar::add_cvt_and_apply_deltas (plan, cvar.get_tuple_var_data (), &cvar);
303
0
    }
304
15.5k
#endif
305
15.5k
    return _hb_subset_table_passthrough (plan, tag);
306
46.7k
  }
307
308
15.5k
  if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED)
309
0
    return _hb_subset_table_passthrough (plan, tag);
310
311
  // Drop table
312
15.5k
  return true;
313
15.5k
}
314
315
static void _attach_accelerator_data (hb_subset_plan_t* plan,
316
                                      hb_face_t* face /* IN/OUT */)
317
0
{
318
0
  if (!plan->inprogress_accelerator) return;
319
320
  // Transfer the accelerator from the plan to us.
321
0
  hb_subset_accelerator_t* accel = plan->inprogress_accelerator;
322
0
  plan->inprogress_accelerator = nullptr;
323
324
0
  if (accel->in_error ())
325
0
  {
326
0
    hb_subset_accelerator_t::destroy (accel);
327
0
    return;
328
0
  }
329
330
  // Populate caches that need access to the final tables.
331
0
  hb_blob_ptr_t<OT::cmap> cmap_ptr (hb_sanitize_context_t ().reference_table<OT::cmap> (face));
332
0
  accel->cmap_cache = OT::cmap::create_filled_cache (cmap_ptr);
333
0
  accel->destroy_cmap_cache = OT::SubtableUnicodesCache::destroy;
334
335
0
  if (!hb_face_set_user_data(face,
336
0
                             hb_subset_accelerator_t::user_data_key(),
337
0
                             accel,
338
0
                             hb_subset_accelerator_t::destroy,
339
0
                             true))
340
0
    hb_subset_accelerator_t::destroy (accel);
341
0
}
342
343
/**
344
 * hb_subset_or_fail:
345
 * @source: font face data to be subset.
346
 * @input: input to use for the subsetting.
347
 *
348
 * Subsets a font according to provided input. Returns nullptr
349
 * if the subset operation fails or the face has no glyphs.
350
 *
351
 * Since: 2.9.0
352
 **/
353
hb_face_t *
354
hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input)
355
15.5k
{
356
15.5k
  if (unlikely (!input || !source)) return nullptr;
357
358
15.5k
  if (unlikely (!source->get_num_glyphs ()))
359
0
  {
360
0
    DEBUG_MSG (SUBSET, nullptr, "No glyphs in source font.");
361
0
    return nullptr;
362
0
  }
363
364
15.5k
  hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (source, input);
365
15.5k
  if (unlikely (!plan)) {
366
0
    return nullptr;
367
0
  }
368
369
15.5k
  hb_face_t * result = hb_subset_plan_execute_or_fail (plan);
370
15.5k
  hb_subset_plan_destroy (plan);
371
15.5k
  return result;
372
15.5k
}
373
374
375
/**
376
 * hb_subset_plan_execute_or_fail:
377
 * @plan: a subsetting plan.
378
 *
379
 * Executes the provided subsetting @plan.
380
 *
381
 * Return value:
382
 * on success returns a reference to generated font subset. If the subsetting operation fails
383
 * returns nullptr.
384
 *
385
 * Since: 4.0.0
386
 **/
387
hb_face_t *
388
hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan)
389
15.5k
{
390
15.5k
  if (unlikely (!plan || plan->in_error ())) {
391
0
    return nullptr;
392
0
  }
393
394
15.5k
  hb_tag_t table_tags[32];
395
15.5k
  unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags);
396
397
15.5k
  hb_set_t subsetted_tags, pending_subset_tags;
398
31.1k
  while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables))
399
15.5k
  {
400
311k
    for (unsigned i = 0; i < num_tables; ++i)
401
295k
    {
402
295k
      hb_tag_t tag = table_tags[i];
403
295k
      if (_should_drop_table (plan, tag)) continue;
404
202k
      pending_subset_tags.add (tag);
405
202k
    }
406
407
15.5k
    offset += num_tables;
408
15.5k
  }
409
410
15.5k
  bool success = true;
411
412
15.5k
  {
413
    // Grouping to deallocate buf before calling hb_face_reference (plan->dest).
414
415
15.5k
    hb_vector_t<char> buf;
416
15.5k
    buf.alloc (8192 - 16);
417
418
31.1k
    while (!pending_subset_tags.is_empty ())
419
15.5k
    {
420
15.5k
      if (subsetted_tags.in_error ()
421
15.5k
    || pending_subset_tags.in_error ()) {
422
0
  success = false;
423
0
  goto end;
424
0
      }
425
426
15.5k
      bool made_changes = false;
427
15.5k
      for (hb_tag_t tag : pending_subset_tags)
428
202k
      {
429
202k
  if (!_dependencies_satisfied (plan, tag,
430
202k
              subsetted_tags,
431
202k
              pending_subset_tags))
432
0
  {
433
    // delayed subsetting for some tables since they might have dependency on other tables
434
    // in some cases: e.g: during instantiating glyf tables, hmetrics/vmetrics are updated
435
    // and saved in subset plan, hmtx/vmtx subsetting need to use these updated metrics values
436
0
    continue;
437
0
  }
438
439
202k
  pending_subset_tags.del (tag);
440
202k
  subsetted_tags.add (tag);
441
202k
  made_changes = true;
442
443
202k
  success = _subset_table (plan, buf, tag);
444
202k
  if (unlikely (!success)) goto end;
445
202k
      }
446
447
15.5k
      if (!made_changes)
448
0
      {
449
0
  DEBUG_MSG (SUBSET, nullptr, "Table dependencies unable to be satisfied. Subset failed.");
450
0
  success = false;
451
0
  goto end;
452
0
      }
453
15.5k
    }
454
15.5k
  }
455
456
15.5k
  if (success && plan->attach_accelerator_data) {
457
0
    _attach_accelerator_data (plan, plan->dest);
458
0
  }
459
460
15.5k
end:
461
15.5k
  return success ? hb_face_reference (plan->dest) : nullptr;
462
15.5k
}