Coverage Report

Created: 2026-10-17 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-open-file.hh
Line
Count
Source
1
/*
2
 * Copyright © 2007,2008,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
#ifndef HB_OPEN_FILE_HH
30
#define HB_OPEN_FILE_HH
31
32
#include "hb-open-type.hh"
33
#include "hb-ot-head-table.hh"
34
35
36
namespace OT {
37
38
/*
39
 *
40
 * The OpenType Font File
41
 *
42
 */
43
44
45
/*
46
 * Organization of an OpenType Font
47
 */
48
49
struct OpenTypeFontFile;
50
struct OpenTypeOffsetTable;
51
struct TTCHeader;
52
53
54
typedef struct TableRecord
55
{
56
0
  int cmp (Tag t) const { return -t.cmp (tag); }
57
58
  HB_INTERNAL static int cmp (const void *pa, const void *pb)
59
0
  {
60
0
    const TableRecord *a = (const TableRecord *) pa;
61
0
    const TableRecord *b = (const TableRecord *) pb;
62
0
    return b->cmp (a->tag);
63
0
  }
64
65
  bool sanitize (hb_sanitize_context_t *c) const
66
0
  {
67
0
    TRACE_SANITIZE (this);
68
0
    return_trace (c->check_struct (this));
69
0
  }
70
71
  Tag   tag;    /* 4-byte identifier. */
72
  CheckSum  checkSum; /* CheckSum for this table. */
73
  Offset32  offset;   /* Offset from beginning of TrueType font
74
         * file. */
75
  HBUINT32  length;   /* Length of this table. */
76
  public:
77
  DEFINE_SIZE_STATIC (16);
78
} OpenTypeTable;
79
80
typedef struct OpenTypeOffsetTable
81
{
82
  friend struct OpenTypeFontFile;
83
84
0
  unsigned int get_table_count () const { return tables.len; }
85
  const TableRecord& get_table (unsigned int i) const
86
0
  { return tables[i]; }
87
  unsigned int get_table_tags (unsigned int  start_offset,
88
             unsigned int *table_count, /* IN/OUT */
89
             hb_tag_t     *table_tags /* OUT */) const
90
0
  {
91
0
    if (table_count && table_tags)
92
0
    {
93
0
      + tables.as_array ().sub_array (start_offset, table_count)
94
0
      | hb_map (&TableRecord::tag)
95
0
      | hb_sink (hb_array (table_tags, *table_count))
96
0
      ;
97
0
    }
98
0
    return tables.len;
99
0
  }
100
  bool find_table_index (hb_tag_t tag, unsigned int *table_index) const
101
0
  {
102
0
    Tag t;
103
0
    t = tag;
104
    /* Use lfind for small fonts; there are fonts that have unsorted table entries;
105
     * those tend to work in other tools, so tolerate them.
106
     * https://github.com/harfbuzz/harfbuzz/issues/3065 */
107
0
    if (tables.len < 16)
108
0
      return tables.lfind (t, table_index, HB_NOT_FOUND_STORE, Index::NOT_FOUND_INDEX);
109
0
    else
110
0
      return tables.bfind (t, table_index, HB_NOT_FOUND_STORE, Index::NOT_FOUND_INDEX);
111
0
  }
112
  const TableRecord& get_table_by_tag (hb_tag_t tag) const
113
0
  {
114
0
    unsigned int table_index;
115
0
    find_table_index (tag, &table_index);
116
0
    return get_table (table_index);
117
0
  }
118
119
  public:
120
121
  template <typename Iterator,
122
      hb_requires ((hb_is_source_of<Iterator, hb_pair_t<hb_tag_t, hb_blob_t *>>::value))>
123
  bool serialize (hb_serialize_context_t *c,
124
      hb_tag_t sfnt_tag,
125
      Iterator it)
126
0
  {
127
0
    TRACE_SERIALIZE (this);
128
    /* Alloc 12 for the OTHeader. */
129
0
    if (unlikely (!c->extend_min (this))) return_trace (false);
130
    /* Write sfntVersion (bytes 0..3). */
131
0
    sfnt_version = sfnt_tag;
132
    /* Take space for numTables, searchRange, entrySelector, RangeShift
133
     * and the TableRecords themselves.  */
134
0
    unsigned num_items = hb_len (it);
135
0
    if (unlikely (!tables.serialize (c, num_items))) return_trace (false);
136
137
0
    const char *dir_end = (const char *) c->head;
138
0
    HBUINT32 *checksum_adjustment = nullptr;
139
140
    /* Write OffsetTables, alloc for and write actual table blobs. */
141
0
    unsigned i = 0;
142
0
    for (hb_pair_t<hb_tag_t, hb_blob_t*> entry : it)
143
0
    {
144
0
      hb_blob_t *blob = entry.second;
145
0
      unsigned len = blob->length;
146
147
      /* Allocate room for the table and copy it. */
148
0
      char *start = (char *) c->allocate_size<void> (len, false);
149
0
      if (unlikely (!start)) return false;
150
151
0
      TableRecord &rec = tables.arrayZ[i];
152
0
      rec.tag = entry.first;
153
0
      rec.length = len;
154
0
      rec.offset = 0;
155
0
      if (unlikely (!c->check_assign (rec.offset,
156
0
              (unsigned) ((char *) start - (char *) this),
157
0
              HB_SERIALIZE_ERROR_OFFSET_OVERFLOW)))
158
0
        return_trace (false);
159
160
0
      if (likely (len))
161
0
  hb_memcpy (start, blob->data, len);
162
163
      /* 4-byte alignment. */
164
0
      c->align (4);
165
0
      const char *end = (const char *) c->head;
166
167
0
      if (entry.first == HB_OT_TAG_head &&
168
0
    (unsigned) (end - start) >= head::static_size)
169
0
      {
170
0
  head *h = (head *) start;
171
0
  checksum_adjustment = &h->checkSumAdjustment;
172
0
  *checksum_adjustment = 0;
173
0
      }
174
175
0
      rec.checkSum.set_for_data (start, end - start);
176
0
      i++;
177
0
    }
178
179
0
    tables.qsort ();
180
181
0
    if (checksum_adjustment)
182
0
    {
183
0
      CheckSum checksum;
184
185
      /* The following line is a slower version of the following block. */
186
      //checksum.set_for_data (this, (const char *) c->head - (const char *) this);
187
0
      checksum.set_for_data (this, dir_end - (const char *) this);
188
0
      for (unsigned int i = 0; i < num_items; i++)
189
0
      {
190
0
  TableRecord &rec = tables.arrayZ[i];
191
0
  checksum = checksum + rec.checkSum;
192
0
      }
193
194
0
      *checksum_adjustment = 0xB1B0AFBAu - checksum;
195
0
    }
196
197
0
    return_trace (true);
198
0
  }
199
200
  bool sanitize (hb_sanitize_context_t *c) const
201
0
  {
202
0
    TRACE_SANITIZE (this);
203
0
    return_trace (c->check_struct (this) && tables.sanitize (c));
204
0
  }
205
206
  protected:
207
  Tag   sfnt_version; /* '\0\001\0\00' if TrueType / 'OTTO' if CFF */
208
  BinSearchArrayOf<TableRecord>
209
    tables;
210
  public:
211
  DEFINE_SIZE_ARRAY (12, tables);
212
} OpenTypeFontFace;
213
214
215
/*
216
 * TrueType Collections
217
 */
218
219
struct TTCHeaderVersion1
220
{
221
  friend struct TTCHeader;
222
223
  using DirectoryList = Array32Of<Offset32To<OpenTypeOffsetTable>>;
224
225
0
  unsigned int get_face_count () const { return get_directory_list ().len; }
226
  const OpenTypeFontFace& get_face (unsigned int i) const
227
0
  { return this+get_directory_list ()[i]; }
228
229
  bool sanitize (hb_sanitize_context_t *c) const
230
0
  {
231
0
    TRACE_SANITIZE (this);
232
#ifndef HB_NO_BEYOND_64K
233
    if (version.minor >= 1)
234
    {
235
      if (unlikely (!table.sanitize_shallow (c)))
236
  return_trace (false);
237
      unsigned extra_size = version.major == 2 ? 12 : 0;
238
      if (unlikely (!c->check_range (&table, table.get_size () + extra_size)))
239
  return_trace (false);
240
      const DirectoryList &table2 = StructAtOffset<DirectoryList> (&table,
241
                   table.get_size () + extra_size);
242
      return_trace (table2.sanitize (c, this));
243
    }
244
#endif
245
0
    return_trace (table.sanitize (c, this));
246
0
  }
247
248
  private:
249
  const DirectoryList& get_directory_list () const
250
0
  {
251
#ifndef HB_NO_BEYOND_64K
252
    if (version.minor >= 1)
253
    {
254
      unsigned extra_size = version.major == 2 ? 12 : 0;
255
      return StructAtOffset<DirectoryList> (&table, table.get_size () + extra_size);
256
    }
257
#endif
258
0
    return table;
259
0
  }
260
261
  protected:
262
  Tag   ttcTag;   /* TrueType Collection ID string: 'ttcf' */
263
  FixedVersion<>version;  /* Version of the TTC Header. */
264
  DirectoryList
265
    table;    /* Array of offsets to the OffsetTable for each font
266
         * from the beginning of the file */
267
  public:
268
  DEFINE_SIZE_ARRAY (12, table);
269
};
270
271
struct TTCHeader
272
{
273
  friend struct OpenTypeFontFile;
274
275
  private:
276
277
  unsigned int get_face_count () const
278
0
  {
279
0
    switch (u.header.version.major) {
280
0
    case 2: /* version 2 is compatible with version 1 */
281
0
    case 1: hb_barrier (); return u.version1.get_face_count ();
282
0
    default:return 0;
283
0
    }
284
0
  }
285
  const OpenTypeFontFace& get_face (unsigned int i) const
286
0
  {
287
0
    switch (u.header.version.major) {
288
0
    case 2: /* version 2 is compatible with version 1 */
289
0
    case 1: hb_barrier (); return u.version1.get_face (i);
290
0
    default:return Null (OpenTypeFontFace);
291
0
    }
292
0
  }
293
294
  bool sanitize (hb_sanitize_context_t *c) const
295
0
  {
296
0
    TRACE_SANITIZE (this);
297
0
    if (unlikely (!u.header.version.sanitize (c))) return_trace (false);
298
0
    hb_barrier ();
299
0
    switch (u.header.version.major) {
300
0
    case 2: /* version 2 is compatible with version 1 */
301
0
    case 1: hb_barrier (); return_trace (u.version1.sanitize (c));
302
0
    default:return_trace (true);
303
0
    }
304
0
  }
305
306
  protected:
307
  union {
308
  struct {
309
  Tag   ttcTag;   /* TrueType Collection ID string: 'ttcf' */
310
  FixedVersion<>version;  /* Version of the TTC Header (1.0 or 2.0),
311
         * 0x00010000u or 0x00020000u */
312
  }     header;
313
  TTCHeaderVersion1 version1;
314
  } u;
315
};
316
317
/*
318
 * Mac Resource Fork
319
 *
320
 * http://mirror.informatimago.com/next/developer.apple.com/documentation/mac/MoreToolbox/MoreToolbox-99.html
321
 */
322
323
struct ResourceRecord
324
{
325
  const OpenTypeFontFace & get_face (const void *data_base) const
326
0
  { return * reinterpret_cast<const OpenTypeFontFace *> ((data_base+offset).arrayZ); }
327
328
  bool sanitize (hb_sanitize_context_t *c,
329
     const void *data_base) const
330
0
  {
331
0
    TRACE_SANITIZE (this);
332
0
    return_trace (c->check_struct (this) &&
333
0
      offset.sanitize (c, data_base) &&
334
0
      hb_barrier () &&
335
0
      get_face (data_base).sanitize (c));
336
0
  }
337
338
  protected:
339
  HBUINT16  id;   /* Resource ID. */
340
  HBINT16 nameOffset; /* Offset from beginning of resource name list
341
         * to resource name, -1 means there is none. */
342
  HBUINT8 attrs;    /* Resource attributes */
343
  NNOffset24To<Array32Of<HBUINT8>>
344
    offset;   /* Offset from beginning of data block to
345
         * data for this resource */
346
  HBUINT32  reserved; /* Reserved for handle to resource */
347
  public:
348
  DEFINE_SIZE_STATIC (12);
349
};
350
351
0
#define HB_TAG_sfnt HB_TAG ('s','f','n','t')
352
353
struct ResourceTypeRecord
354
{
355
  unsigned int get_resource_count () const
356
0
  { return tag == HB_TAG_sfnt ? resCountM1 + 1 : 0; }
357
358
0
  bool is_sfnt () const { return tag == HB_TAG_sfnt; }
359
360
  const ResourceRecord& get_resource_record (unsigned int i,
361
               const void *type_base) const
362
0
  { return (type_base+resourcesZ).as_array (get_resource_count ())[i]; }
363
364
  bool sanitize (hb_sanitize_context_t *c,
365
     const void *type_base,
366
     const void *data_base) const
367
0
  {
368
0
    TRACE_SANITIZE (this);
369
0
    return_trace (c->check_struct (this) &&
370
0
      hb_barrier () &&
371
0
      resourcesZ.sanitize (c, type_base,
372
0
               get_resource_count (),
373
0
               data_base));
374
0
  }
375
376
  protected:
377
  Tag   tag;    /* Resource type. */
378
  HBUINT16  resCountM1; /* Number of resources minus 1. */
379
  NNOffset16To<UnsizedArrayOf<ResourceRecord>>
380
    resourcesZ; /* Offset from beginning of resource type list
381
         * to reference item list for this type. */
382
  public:
383
  DEFINE_SIZE_STATIC (8);
384
};
385
386
struct ResourceMap
387
{
388
  unsigned int get_face_count () const
389
0
  {
390
0
    unsigned int count = get_type_count ();
391
0
    for (unsigned int i = 0; i < count; i++)
392
0
    {
393
0
      const ResourceTypeRecord& type = get_type_record (i);
394
0
      if (type.is_sfnt ())
395
0
  return type.get_resource_count ();
396
0
    }
397
0
    return 0;
398
0
  }
399
400
  const OpenTypeFontFace& get_face (unsigned int idx,
401
            const void *data_base) const
402
0
  {
403
0
    unsigned int count = get_type_count ();
404
0
    for (unsigned int i = 0; i < count; i++)
405
0
    {
406
0
      const ResourceTypeRecord& type = get_type_record (i);
407
      /* The check for idx < count is here because ResourceRecord is NOT null-safe.
408
       * Because an offset of 0 there does NOT mean null. */
409
0
      if (type.is_sfnt () && idx < type.get_resource_count ())
410
0
  return type.get_resource_record (idx, &(this+typeList)).get_face (data_base);
411
0
    }
412
0
    return Null (OpenTypeFontFace);
413
0
  }
414
415
  bool sanitize (hb_sanitize_context_t *c, const void *data_base) const
416
0
  {
417
0
    TRACE_SANITIZE (this);
418
0
    return_trace (c->check_struct (this) &&
419
0
      hb_barrier () &&
420
0
      typeList.sanitize (c, this,
421
0
             &(this+typeList),
422
0
             data_base));
423
0
  }
424
425
  private:
426
0
  unsigned int get_type_count () const { return (this+typeList).lenM1 + 1; }
427
428
  const ResourceTypeRecord& get_type_record (unsigned int i) const
429
0
  { return (this+typeList)[i]; }
430
431
  protected:
432
  HBUINT8 reserved0[16];  /* Reserved for copy of resource header */
433
  HBUINT32  reserved1;  /* Reserved for handle to next resource map */
434
  HBUINT16  resreved2;  /* Reserved for file reference number */
435
  HBUINT16  attrs;    /* Resource fork attribute */
436
  NNOffset16To<ArrayOfM1<ResourceTypeRecord>>
437
    typeList; /* Offset from beginning of map to
438
         * resource type list */
439
  Offset16  nameList; /* Offset from beginning of map to
440
         * resource name list */
441
  public:
442
  DEFINE_SIZE_STATIC (28);
443
};
444
445
struct ResourceForkHeader
446
{
447
  unsigned int get_face_count () const
448
0
  { return (this+map).get_face_count (); }
449
450
  const OpenTypeFontFace& get_face (unsigned int idx,
451
            unsigned int *base_offset = nullptr) const
452
0
  {
453
0
    const OpenTypeFontFace &face = (this+map).get_face (idx, &(this+data));
454
0
    if (base_offset)
455
0
      *base_offset = (const char *) &face - (const char *) this;
456
0
    return face;
457
0
  }
458
459
  bool sanitize (hb_sanitize_context_t *c) const
460
0
  {
461
0
    TRACE_SANITIZE (this);
462
0
    return_trace (c->check_struct (this) &&
463
0
      hb_barrier () &&
464
0
      data.sanitize (c, this, dataLen) &&
465
0
      map.sanitize (c, this, &(this+data)));
466
0
  }
467
468
  protected:
469
  NNOffset32To<UnsizedArrayOf<HBUINT8>>
470
    data;   /* Offset from beginning of resource fork
471
         * to resource data */
472
  NNOffset32To<ResourceMap >
473
    map;    /* Offset from beginning of resource fork
474
         * to resource map */
475
  HBUINT32  dataLen;  /* Length of resource data */
476
  HBUINT32  mapLen;   /* Length of resource map */
477
  public:
478
  DEFINE_SIZE_STATIC (16);
479
};
480
481
/*
482
 * OpenType Font File
483
 */
484
485
struct OpenTypeFontFile
486
{
487
  enum {
488
    CFFTag    = HB_TAG ('O','T','T','O'), /* OpenType with Postscript outlines */
489
    TrueTypeTag   = HB_TAG ( 0 , 1 , 0 , 0 ), /* OpenType with TrueType outlines */
490
    TTCTag    = HB_TAG ('t','t','c','f'), /* TrueType Collection */
491
    DFontTag    = HB_TAG ( 0 , 0 , 1 , 0 ), /* DFont Mac Resource Fork */
492
    TrueTag   = HB_TAG ('t','r','u','e'), /* Obsolete Apple TrueType */
493
    Typ1Tag   = HB_TAG ('t','y','p','1')  /* Obsolete Apple Type1 font in SFNT container */
494
  };
495
496
0
  hb_tag_t get_tag () const { return u.tag.v; }
497
498
  unsigned int get_face_count () const
499
0
  {
500
0
    switch (u.tag.v) {
501
0
    case CFFTag:  /* All the non-collection tags */
502
0
    case TrueTag:
503
0
    case Typ1Tag:
504
0
    case TrueTypeTag: return 1;
505
0
    case TTCTag:  hb_barrier (); return u.ttcHeader.get_face_count ();
506
0
    case DFontTag:  hb_barrier (); return u.rfHeader.get_face_count ();
507
0
    default:    return 0;
508
0
    }
509
0
  }
510
  const OpenTypeFontFace& get_face (unsigned int i, unsigned int *base_offset = nullptr) const
511
0
  {
512
0
    if (base_offset)
513
0
      *base_offset = 0;
514
0
    switch (u.tag.v) {
515
    /* Note: for non-collection SFNT data we ignore index.  This is because
516
     * Apple dfont container is a container of SFNT's.  So each SFNT is a
517
     * non-TTC, but the index is more than zero. */
518
0
    case CFFTag:  /* All the non-collection tags */
519
0
    case TrueTag:
520
0
    case Typ1Tag:
521
0
    case TrueTypeTag: hb_barrier (); return u.fontFace;
522
0
    case TTCTag:  hb_barrier (); return u.ttcHeader.get_face (i);
523
0
    case DFontTag:  hb_barrier (); return u.rfHeader.get_face (i, base_offset);
524
0
    default:    return Null (OpenTypeFontFace);
525
0
    }
526
0
  }
527
528
  template <typename Iterator,
529
      hb_requires ((hb_is_source_of<Iterator, hb_pair_t<hb_tag_t, hb_blob_t *>>::value))>
530
  bool serialize_single (hb_serialize_context_t *c,
531
       hb_tag_t sfnt_tag,
532
       Iterator items)
533
0
  {
534
0
    TRACE_SERIALIZE (this);
535
0
    assert (sfnt_tag != TTCTag);
536
0
    if (unlikely (!c->extend_min (this))) return_trace (false);
537
0
    return_trace (u.fontFace.serialize (c, sfnt_tag, items));
538
0
  }
539
540
  bool sanitize (hb_sanitize_context_t *c) const
541
0
  {
542
0
    TRACE_SANITIZE (this);
543
0
    if (unlikely (!u.tag.v.sanitize (c))) return_trace (false);
544
0
    hb_barrier ();
545
0
    switch (u.tag.v) {
546
0
    case CFFTag:  /* All the non-collection tags */
547
0
    case TrueTag:
548
0
    case Typ1Tag:
549
0
    case TrueTypeTag: hb_barrier (); return_trace (u.fontFace.sanitize (c));
550
0
    case TTCTag:  hb_barrier (); return_trace (u.ttcHeader.sanitize (c));
551
0
    case DFontTag:  hb_barrier (); return_trace (u.rfHeader.sanitize (c));
552
0
    default:    return_trace (true);
553
0
    }
554
0
  }
555
556
  protected:
557
  union {
558
  struct { Tag v; } tag;    /* 4-byte identifier. */
559
  OpenTypeFontFace  fontFace;
560
  TTCHeader   ttcHeader;
561
  ResourceForkHeader  rfHeader;
562
  } u;
563
  public:
564
  DEFINE_SIZE_UNION (4, tag.v);
565
};
566
567
568
} /* namespace OT */
569
570
571
#endif /* HB_OPEN_FILE_HH */