Coverage Report

Created: 2026-03-31 11:00

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