qpdf-compress 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ // shared encoding lookup tables for WinAnsiEncoding and MacRomanEncoding
2
+ #pragma once
3
+
4
+ #include <cstdint>
5
+
6
+ // WinAnsiEncoding codes 0x80-0x9F differ from Latin-1/Unicode.
7
+ // All other codes (0x00-0x7F, 0xA0-0xFF) match their Unicode code points.
8
+ inline uint16_t winAnsiToUnicode(uint8_t code) {
9
+ static const uint16_t table[32] = {
10
+ 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
11
+ 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
12
+ 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
13
+ 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
14
+ };
15
+ if (code >= 0x80 && code <= 0x9F)
16
+ return table[code - 0x80];
17
+ return code;
18
+ }
19
+
20
+ // MacRomanEncoding — codes 0x80-0xFF map to various Unicode code points.
21
+ inline uint16_t macRomanToUnicode(uint8_t code) {
22
+ static const uint16_t table[128] = {
23
+ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0,
24
+ 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB,
25
+ 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6,
26
+ 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3,
27
+ 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8,
28
+ 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5,
29
+ 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6,
30
+ 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
31
+ 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, 0x2013,
32
+ 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178,
33
+ 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A,
34
+ 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE,
35
+ 0x00CF, 0x00CC, 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9,
36
+ 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD,
37
+ 0x02DB, 0x02C7,
38
+ };
39
+ if (code >= 0x80)
40
+ return table[code - 0x80];
41
+ return code;
42
+ }
@@ -1,480 +1,183 @@
1
1
  #include "font_subset.h"
2
2
 
3
- #include <algorithm>
4
3
  #include <cstring>
5
- #include <map>
6
- #include <queue>
4
+ #include <string>
5
+ #include <vector>
7
6
 
8
- // ---------------------------------------------------------------------------
9
- // TrueType binary helpers — big-endian reads
10
- // ---------------------------------------------------------------------------
11
-
12
- static uint16_t readU16(const uint8_t *p) {
13
- return static_cast<uint16_t>((p[0] << 8) | p[1]);
14
- }
15
-
16
- static uint32_t readU32(const uint8_t *p) {
17
- return (static_cast<uint32_t>(p[0]) << 24) |
18
- (static_cast<uint32_t>(p[1]) << 16) |
19
- (static_cast<uint32_t>(p[2]) << 8) | p[3];
20
- }
21
-
22
- static int16_t readI16(const uint8_t *p) {
23
- return static_cast<int16_t>(readU16(p));
24
- }
25
-
26
- static void writeU16(uint8_t *p, uint16_t v) {
27
- p[0] = static_cast<uint8_t>(v >> 8);
28
- p[1] = static_cast<uint8_t>(v);
29
- }
30
-
31
- static void writeU32(uint8_t *p, uint32_t v) {
32
- p[0] = static_cast<uint8_t>(v >> 24);
33
- p[1] = static_cast<uint8_t>(v >> 16);
34
- p[2] = static_cast<uint8_t>(v >> 8);
35
- p[3] = static_cast<uint8_t>(v);
36
- }
7
+ #include <hb-subset.h>
8
+ #include <hb.h>
37
9
 
38
10
  // ---------------------------------------------------------------------------
39
- // Table directory parsing
11
+ // Glyph lookup using HarfBuzz — replaces manual cmap parsing
40
12
  // ---------------------------------------------------------------------------
41
13
 
42
- struct TableRecord {
43
- uint32_t tag;
44
- uint32_t checksum;
45
- uint32_t offset;
46
- uint32_t length;
47
- };
48
-
49
- static uint32_t tag(const char *s) {
50
- return (static_cast<uint32_t>(s[0]) << 24) |
51
- (static_cast<uint32_t>(s[1]) << 16) |
52
- (static_cast<uint32_t>(s[2]) << 8) | s[3];
53
- }
54
-
55
- static bool parseTables(const uint8_t *data, size_t size,
56
- std::map<uint32_t, TableRecord> &tables) {
57
- if (size < 12)
58
- return false;
59
-
60
- uint16_t numTables = readU16(data + 4);
61
- if (size < 12 + static_cast<size_t>(numTables) * 16)
62
- return false;
63
-
64
- for (uint16_t i = 0; i < numTables; ++i) {
65
- const uint8_t *entry = data + 12 + i * 16;
66
- TableRecord rec;
67
- rec.tag = readU32(entry);
68
- rec.checksum = readU32(entry + 4);
69
- rec.offset = readU32(entry + 8);
70
- rec.length = readU32(entry + 12);
71
-
72
- if (rec.offset + rec.length > size)
73
- return false;
74
-
75
- tables[rec.tag] = rec;
76
- }
77
- return true;
14
+ // helper: create an hb_face from raw font data (HarfBuzz owns a copy)
15
+ static hb_face_t *createFace(const uint8_t *data, size_t size) {
16
+ hb_blob_t *blob = hb_blob_create(reinterpret_cast<const char *>(data), size,
17
+ HB_MEMORY_MODE_DUPLICATE, nullptr, nullptr);
18
+ hb_face_t *face = hb_face_create(blob, 0);
19
+ hb_blob_destroy(blob);
20
+ return face;
78
21
  }
79
22
 
80
- // ---------------------------------------------------------------------------
81
- // cmap parsing — extract character code → glyph ID mapping
82
- // ---------------------------------------------------------------------------
83
-
84
23
  std::set<uint16_t> mapCodesToGlyphIds(const uint8_t *data, size_t size,
85
24
  const std::set<uint16_t> &charCodes) {
86
25
  std::set<uint16_t> glyphIds;
87
26
  glyphIds.insert(0); // always keep .notdef
88
27
 
89
- std::map<uint32_t, TableRecord> tables;
90
- if (!parseTables(data, size, tables))
91
- return glyphIds;
92
-
93
- auto it = tables.find(tag("cmap"));
94
- if (it == tables.end())
95
- return glyphIds;
96
-
97
- const uint8_t *cmap = data + it->second.offset;
98
- size_t cmapLen = it->second.length;
99
- if (cmapLen < 4)
28
+ hb_face_t *face = createFace(data, size);
29
+ if (hb_face_get_glyph_count(face) == 0) {
30
+ hb_face_destroy(face);
100
31
  return glyphIds;
101
-
102
- uint16_t numSubtables = readU16(cmap + 2);
103
-
104
- // find a suitable subtable (prefer format 4 — the most common)
105
- const uint8_t *subtable = nullptr;
106
- for (uint16_t i = 0; i < numSubtables; ++i) {
107
- if (4 + i * 8 + 8 > cmapLen)
108
- break;
109
- const uint8_t *entry = cmap + 4 + i * 8;
110
- uint32_t subtableOffset = readU32(entry + 4);
111
- if (subtableOffset + 2 > cmapLen)
112
- continue;
113
-
114
- uint16_t format = readU16(cmap + subtableOffset);
115
- if (format == 4) {
116
- subtable = cmap + subtableOffset;
117
- break;
118
- }
119
32
  }
120
33
 
121
- // fallback: use first subtable
122
- if (!subtable && numSubtables > 0) {
123
- uint32_t subtableOffset = readU32(cmap + 4 + 4);
124
- if (subtableOffset + 2 <= cmapLen)
125
- subtable = cmap + subtableOffset;
126
- }
127
-
128
- if (!subtable)
129
- return glyphIds;
130
-
131
- uint16_t format = readU16(subtable);
132
-
133
- if (format == 4) {
134
- // format 4: segment mapping to delta values
135
- if (subtable + 14 > cmap + cmapLen)
136
- return glyphIds;
137
-
138
- uint16_t segCount = readU16(subtable + 6) / 2;
139
- const uint8_t *endCodes = subtable + 14;
140
- const uint8_t *startCodes =
141
- endCodes + segCount * 2 + 2; // +2 for reservedPad
142
- const uint8_t *idDeltas = startCodes + segCount * 2;
143
- const uint8_t *idRangeOffsets = idDeltas + segCount * 2;
144
-
145
- // bounds check
146
- if (idRangeOffsets + segCount * 2 > cmap + cmapLen)
147
- return glyphIds;
148
-
149
- for (uint16_t code : charCodes) {
150
- for (uint16_t seg = 0; seg < segCount; ++seg) {
151
- uint16_t endCode = readU16(endCodes + seg * 2);
152
- uint16_t startCode = readU16(startCodes + seg * 2);
153
-
154
- if (code < startCode || code > endCode)
155
- continue;
156
-
157
- uint16_t rangeOffset = readU16(idRangeOffsets + seg * 2);
158
- uint16_t delta = readU16(idDeltas + seg * 2);
159
-
160
- uint16_t glyphId;
161
- if (rangeOffset == 0) {
162
- glyphId = static_cast<uint16_t>((code + delta) & 0xFFFF);
163
- } else {
164
- const uint8_t *glyphIdAddr =
165
- idRangeOffsets + seg * 2 + rangeOffset +
166
- 2 * static_cast<uint16_t>(code - startCode);
167
- if (glyphIdAddr + 2 > cmap + cmapLen)
168
- break;
169
- glyphId = readU16(glyphIdAddr);
170
- if (glyphId != 0)
171
- glyphId = static_cast<uint16_t>((glyphId + delta) & 0xFFFF);
172
- }
173
-
174
- if (glyphId != 0)
175
- glyphIds.insert(glyphId);
176
- break;
177
- }
178
- }
179
- } else if (format == 0) {
180
- // format 0: byte encoding table
181
- if (subtable + 6 + 256 > cmap + cmapLen)
182
- return glyphIds;
183
- for (uint16_t code : charCodes) {
184
- if (code < 256) {
185
- uint8_t gid = subtable[6 + code];
186
- if (gid != 0)
187
- glyphIds.insert(gid);
188
- }
34
+ // collect Unicode → GID mapping from ALL cmap subtables (not just the
35
+ // preferred one). hb_font_get_nominal_glyph only checks the preferred
36
+ // subtable and can miss glyphs in fonts with multiple cmap encodings.
37
+ hb_map_t *mapping = hb_map_create();
38
+ hb_set_t *unicodes = hb_set_create();
39
+ hb_face_collect_nominal_glyph_mapping(face, mapping, unicodes);
40
+
41
+ for (uint16_t code : charCodes) {
42
+ hb_codepoint_t glyph = hb_map_get(mapping, code);
43
+ if (glyph != HB_MAP_VALUE_INVALID && glyph != 0)
44
+ glyphIds.insert(static_cast<uint16_t>(glyph & 0xFFFF));
45
+
46
+ // try 0xF000 offset for symbol fonts (Windows Symbol encoding)
47
+ if ((glyph == HB_MAP_VALUE_INVALID || glyph == 0) && code < 256) {
48
+ glyph = hb_map_get(mapping, code + 0xF000);
49
+ if (glyph != HB_MAP_VALUE_INVALID && glyph != 0)
50
+ glyphIds.insert(static_cast<uint16_t>(glyph & 0xFFFF));
189
51
  }
190
52
  }
191
53
 
54
+ hb_set_destroy(unicodes);
55
+ hb_map_destroy(mapping);
56
+ hb_face_destroy(face);
192
57
  return glyphIds;
193
58
  }
194
59
 
195
- // ---------------------------------------------------------------------------
196
- // Collect composite glyph dependencies
197
- // ---------------------------------------------------------------------------
198
-
199
- static void collectCompositeGlyphs(const uint8_t *glyfData, size_t glyfLen,
200
- const uint8_t *locaData, bool locaLong,
201
- uint16_t numGlyphs,
202
- std::set<uint16_t> &glyphIds) {
203
- std::queue<uint16_t> toProcess;
204
- for (uint16_t gid : glyphIds)
205
- toProcess.push(gid);
206
-
207
- while (!toProcess.empty()) {
208
- uint16_t gid = toProcess.front();
209
- toProcess.pop();
210
-
211
- if (gid >= numGlyphs)
212
- continue;
213
-
214
- uint32_t offset, nextOffset;
215
- if (locaLong) {
216
- offset = readU32(locaData + gid * 4);
217
- nextOffset = readU32(locaData + (gid + 1) * 4);
218
- } else {
219
- offset = static_cast<uint32_t>(readU16(locaData + gid * 2)) * 2;
220
- nextOffset = static_cast<uint32_t>(readU16(locaData + (gid + 1) * 2)) * 2;
221
- }
222
-
223
- if (offset >= nextOffset || offset >= glyfLen)
224
- continue;
225
-
226
- const uint8_t *glyph = glyfData + offset;
227
- size_t glyphLen = nextOffset - offset;
228
- if (glyphLen < 10)
229
- continue;
230
-
231
- int16_t numContours = readI16(glyph);
232
- if (numContours >= 0)
233
- continue; // simple glyph, no dependencies
234
-
235
- // composite glyph — parse component records
236
- size_t pos = 10; // skip header
237
- while (pos + 4 <= glyphLen) {
238
- uint16_t flags = readU16(glyph + pos);
239
- uint16_t componentGid = readU16(glyph + pos + 2);
240
- pos += 4;
241
-
242
- if (componentGid < numGlyphs &&
243
- glyphIds.find(componentGid) == glyphIds.end()) {
244
- glyphIds.insert(componentGid);
245
- toProcess.push(componentGid);
246
- }
60
+ std::set<uint16_t>
61
+ mapGlyphNamesToGlyphIds(const uint8_t *data, size_t size,
62
+ const std::vector<std::string> &names) {
63
+ std::set<uint16_t> glyphIds;
64
+ glyphIds.insert(0); // always keep .notdef
247
65
 
248
- // skip arguments based on flags
249
- if (flags & 0x0001) // ARG_1_AND_2_ARE_WORDS
250
- pos += 4;
251
- else
252
- pos += 2;
66
+ hb_face_t *face = createFace(data, size);
67
+ if (hb_face_get_glyph_count(face) == 0) {
68
+ hb_face_destroy(face);
69
+ return glyphIds;
70
+ }
253
71
 
254
- if (flags & 0x0008) // WE_HAVE_A_SCALE
255
- pos += 2;
256
- else if (flags & 0x0040) // WE_HAVE_AN_X_AND_Y_SCALE
257
- pos += 4;
258
- else if (flags & 0x0080) // WE_HAVE_A_TWO_BY_TWO
259
- pos += 8;
72
+ hb_font_t *font = hb_font_create(face);
260
73
 
261
- if (!(flags & 0x0020)) // MORE_COMPONENTS
262
- break;
263
- }
74
+ for (const auto &name : names) {
75
+ hb_codepoint_t glyph = 0;
76
+ if (hb_font_get_glyph_from_name(font, name.c_str(),
77
+ static_cast<int>(name.size()), &glyph) &&
78
+ glyph != 0)
79
+ glyphIds.insert(static_cast<uint16_t>(glyph & 0xFFFF));
264
80
  }
81
+
82
+ hb_font_destroy(font);
83
+ hb_face_destroy(face);
84
+ return glyphIds;
265
85
  }
266
86
 
267
87
  // ---------------------------------------------------------------------------
268
- // TrueType font subsetting — keep only used glyphs
88
+ // Font subsetting using HarfBuzz hb-subset (TrueType and CFF/OpenType)
269
89
  // ---------------------------------------------------------------------------
270
90
 
271
- static uint32_t calcChecksum(const uint8_t *data, size_t length) {
272
- uint32_t sum = 0;
273
- size_t nLongs = (length + 3) / 4;
274
- for (size_t i = 0; i < nLongs; ++i) {
275
- uint32_t val = 0;
276
- for (size_t j = 0; j < 4; ++j) {
277
- size_t idx = i * 4 + j;
278
- val = (val << 8) | (idx < length ? data[idx] : 0);
279
- }
280
- sum += val;
281
- }
282
- return sum;
91
+ static uint32_t readU32(const uint8_t *p) {
92
+ return (static_cast<uint32_t>(p[0]) << 24) |
93
+ (static_cast<uint32_t>(p[1]) << 16) |
94
+ (static_cast<uint32_t>(p[2]) << 8) | p[3];
283
95
  }
284
96
 
285
- bool subsetTrueTypeFont(const uint8_t *data, size_t size,
286
- const std::set<uint16_t> &usedGlyphIds,
287
- std::vector<uint8_t> &output) {
288
- std::map<uint32_t, TableRecord> tables;
289
- if (!parseTables(data, size, tables))
290
- return false;
291
-
292
- // required tables
293
- auto headIt = tables.find(tag("head"));
294
- auto maxpIt = tables.find(tag("maxp"));
295
- auto locaIt = tables.find(tag("loca"));
296
- auto glyfIt = tables.find(tag("glyf"));
297
-
298
- if (headIt == tables.end() || maxpIt == tables.end() ||
299
- locaIt == tables.end() || glyfIt == tables.end())
97
+ bool subsetFont(const uint8_t *data, size_t size,
98
+ const std::set<uint16_t> &usedGlyphIds,
99
+ std::vector<uint8_t> &output, bool preserveCmap) {
100
+ // validate font header — accept TrueType (0x00010000, 'true') and
101
+ // OpenType-CFF ('OTTO')
102
+ if (size < 12)
300
103
  return false;
301
-
302
- const uint8_t *headData = data + headIt->second.offset;
303
- if (headIt->second.length < 54)
104
+ uint32_t sfVersion = readU32(data);
105
+ if (sfVersion != 0x00010000 && sfVersion != 0x74727565 &&
106
+ sfVersion != 0x4F54544F) // OTTO
304
107
  return false;
305
108
 
306
- bool locaLong = readI16(headData + 50) == 1;
109
+ // let HarfBuzz own a copy of the font data to avoid any lifetime issues
110
+ hb_blob_t *blob = hb_blob_create(reinterpret_cast<const char *>(data), size,
111
+ HB_MEMORY_MODE_DUPLICATE, nullptr, nullptr);
112
+ hb_face_t *face = hb_face_create(blob, 0);
113
+ hb_blob_destroy(blob);
307
114
 
308
- const uint8_t *maxpData = data + maxpIt->second.offset;
309
- if (maxpIt->second.length < 6)
115
+ if (hb_face_get_glyph_count(face) == 0) {
116
+ hb_face_destroy(face);
310
117
  return false;
311
- uint16_t numGlyphs = readU16(maxpData + 4);
312
-
313
- const uint8_t *locaData = data + locaIt->second.offset;
314
- const uint8_t *glyfData = data + glyfIt->second.offset;
315
- size_t glyfLen = glyfIt->second.length;
316
-
317
- // collect all needed glyphs including composite dependencies
318
- auto allGlyphs = usedGlyphIds;
319
- allGlyphs.insert(0); // always keep .notdef
320
- collectCompositeGlyphs(glyfData, glyfLen, locaData, locaLong, numGlyphs,
321
- allGlyphs);
322
-
323
- // build new glyf table — map old glyph IDs to new glyph data
324
- // we preserve old glyph IDs (don't remap) to keep cmap valid —
325
- // instead we zero out unused glyph slots
326
- std::vector<uint8_t> newGlyf;
327
- std::vector<uint32_t> newLoca(numGlyphs + 1);
328
-
329
- for (uint16_t gid = 0; gid < numGlyphs; ++gid) {
330
- newLoca[gid] = static_cast<uint32_t>(newGlyf.size());
331
-
332
- if (allGlyphs.find(gid) == allGlyphs.end())
333
- continue; // empty glyph — loca points to same offset as next
334
-
335
- uint32_t offset, nextOffset;
336
- if (locaLong) {
337
- if ((gid + 1) * 4 + 4 > locaIt->second.length)
338
- continue;
339
- offset = readU32(locaData + gid * 4);
340
- nextOffset = readU32(locaData + (gid + 1) * 4);
341
- } else {
342
- if ((gid + 1) * 2 + 2 > locaIt->second.length)
343
- continue;
344
- offset = static_cast<uint32_t>(readU16(locaData + gid * 2)) * 2;
345
- nextOffset = static_cast<uint32_t>(readU16(locaData + (gid + 1) * 2)) * 2;
346
- }
347
-
348
- if (offset >= nextOffset || offset >= glyfLen)
349
- continue;
350
-
351
- uint32_t len =
352
- std::min(nextOffset - offset, static_cast<uint32_t>(glyfLen - offset));
353
- newGlyf.insert(newGlyf.end(), glyfData + offset, glyfData + offset + len);
354
-
355
- // pad to 2-byte boundary (for short loca) or 4-byte boundary (long loca)
356
- size_t align = locaLong ? 4 : 2;
357
- while (newGlyf.size() % align != 0)
358
- newGlyf.push_back(0);
359
118
  }
360
- newLoca[numGlyphs] = static_cast<uint32_t>(newGlyf.size());
361
-
362
- // build new loca table
363
- std::vector<uint8_t> newLocaData;
364
- if (locaLong) {
365
- newLocaData.resize((numGlyphs + 1) * 4);
366
- for (uint16_t i = 0; i <= numGlyphs; ++i)
367
- writeU32(newLocaData.data() + i * 4, newLoca[i]);
368
- } else {
369
- newLocaData.resize((numGlyphs + 1) * 2);
370
- for (uint16_t i = 0; i <= numGlyphs; ++i)
371
- writeU16(newLocaData.data() + i * 2,
372
- static_cast<uint16_t>(newLoca[i] / 2));
373
- }
374
-
375
- // subset hmtx: zero widths for unused glyphs
376
- auto hheaIt = tables.find(tag("hhea"));
377
- auto hmtxIt = tables.find(tag("hmtx"));
378
- std::vector<uint8_t> newHmtx;
379
-
380
- if (hheaIt != tables.end() && hmtxIt != tables.end() &&
381
- hheaIt->second.length >= 36) {
382
- uint16_t numHMetrics = readU16(data + hheaIt->second.offset + 34);
383
- const uint8_t *hmtxData = data + hmtxIt->second.offset;
384
- size_t hmtxLen = hmtxIt->second.length;
385
119
 
386
- newHmtx.assign(hmtxData, hmtxData + hmtxLen);
387
-
388
- for (uint16_t gid = 0; gid < numGlyphs; ++gid) {
389
- if (allGlyphs.find(gid) != allGlyphs.end())
390
- continue;
391
-
392
- if (gid < numHMetrics) {
393
- // long metric entry: advanceWidth(2) + lsb(2)
394
- size_t off = static_cast<size_t>(gid) * 4;
395
- if (off + 4 <= newHmtx.size()) {
396
- writeU16(newHmtx.data() + off, 0);
397
- writeU16(newHmtx.data() + off + 2, 0);
398
- }
399
- } else {
400
- // short metric: just lsb(2) after the long entries
401
- size_t off = static_cast<size_t>(numHMetrics) * 4 +
402
- static_cast<size_t>(gid - numHMetrics) * 2;
403
- if (off + 2 <= newHmtx.size())
404
- writeU16(newHmtx.data() + off, 0);
405
- }
406
- }
120
+ hb_subset_input_t *input = hb_subset_input_create_or_fail();
121
+ if (!input) {
122
+ hb_face_destroy(face);
123
+ return false;
407
124
  }
408
125
 
409
- // collect tables to write (all original tables, replacing glyf/loca/hmtx)
410
- struct OutputTable {
411
- uint32_t tag;
412
- std::vector<uint8_t> data;
413
- };
414
- std::vector<OutputTable> outTables;
415
-
416
- for (auto &[tableTag, rec] : tables) {
417
- OutputTable ot;
418
- ot.tag = tableTag;
419
-
420
- if (tableTag == tag("glyf")) {
421
- ot.data = newGlyf;
422
- } else if (tableTag == tag("loca")) {
423
- ot.data = newLocaData;
424
- } else if (tableTag == tag("hmtx") && !newHmtx.empty()) {
425
- ot.data = newHmtx;
426
- } else {
427
- ot.data.assign(data + rec.offset, data + rec.offset + rec.length);
428
- }
429
-
430
- outTables.push_back(std::move(ot));
126
+ // retain original glyph IDs so all existing PDF text references remain
127
+ // valid — unused glyph slots are zeroed out
128
+ hb_subset_input_set_flags(input, HB_SUBSET_FLAGS_RETAIN_GIDS);
129
+
130
+ // drop font tables unnecessary for PDF rendering
131
+ hb_set_t *dropTables =
132
+ hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG);
133
+ hb_set_add(dropTables, HB_TAG('G', 'P', 'O', 'S')); // OpenType positioning
134
+ hb_set_add(dropTables, HB_TAG('G', 'S', 'U', 'B')); // OpenType substitution
135
+ hb_set_add(dropTables, HB_TAG('G', 'D', 'E', 'F')); // OpenType definitions
136
+ hb_set_add(dropTables, HB_TAG('D', 'S', 'I', 'G')); // digital signature
137
+ hb_set_add(dropTables, HB_TAG('k', 'e', 'r', 'n')); // legacy kerning
138
+ hb_set_add(dropTables, HB_TAG('n', 'a', 'm', 'e')); // font naming strings
139
+ hb_set_add(dropTables, HB_TAG('g', 'a', 's', 'p')); // grid-fitting hints
140
+ hb_set_add(dropTables,
141
+ HB_TAG('h', 'd', 'm', 'x')); // horizontal device metrics
142
+ hb_set_add(dropTables, HB_TAG('L', 'T', 'S', 'H')); // linear threshold
143
+ hb_set_add(dropTables, HB_TAG('V', 'D', 'M', 'X')); // device metrics
144
+
145
+ // preserve the cmap table unchanged for simple TrueType fonts —
146
+ // PDF viewers use it to resolve character codes to glyph IDs.
147
+ // for CID fonts, PDF viewers use /CIDToGIDMap instead, so the cmap
148
+ // can be freely subset.
149
+ if (preserveCmap) {
150
+ hb_set_t *noSubsetTables =
151
+ hb_subset_input_set(input, HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG);
152
+ hb_set_add(noSubsetTables, HB_TAG('c', 'm', 'a', 'p'));
431
153
  }
432
154
 
433
- // sort by tag (recommended for efficient access)
434
- std::sort(
435
- outTables.begin(), outTables.end(),
436
- [](const OutputTable &a, const OutputTable &b) { return a.tag < b.tag; });
155
+ // populate the glyph set to keep
156
+ hb_set_t *glyphs = hb_subset_input_glyph_set(input);
157
+ for (uint16_t gid : usedGlyphIds)
158
+ hb_set_add(glyphs, gid);
437
159
 
438
- // calculate output size
439
- uint16_t numOutTables = static_cast<uint16_t>(outTables.size());
440
- size_t headerSize = 12 + numOutTables * 16;
441
- size_t totalSize = headerSize;
442
- for (auto &ot : outTables)
443
- totalSize += ((ot.data.size() + 3) / 4) * 4; // pad to 4 bytes
160
+ hb_face_t *subset = hb_subset_or_fail(face, input);
161
+ hb_subset_input_destroy(input);
162
+ hb_face_destroy(face);
444
163
 
445
- output.resize(totalSize, 0);
446
- uint8_t *out = output.data();
164
+ if (!subset)
165
+ return false;
447
166
 
448
- // write offset table
449
- writeU32(out, readU32(data)); // sfVersion (same as original)
450
- writeU16(out + 4, numOutTables);
167
+ hb_blob_t *result = hb_face_reference_blob(subset);
168
+ unsigned int length = 0;
169
+ const char *resultData = hb_blob_get_data(result, &length);
451
170
 
452
- // searchRange, entrySelector, rangeShift
453
- uint16_t searchRange = 1;
454
- uint16_t entrySelector = 0;
455
- while (searchRange * 2 <= numOutTables) {
456
- searchRange *= 2;
457
- ++entrySelector;
171
+ if (length == 0 || !resultData) {
172
+ hb_blob_destroy(result);
173
+ hb_face_destroy(subset);
174
+ return false;
458
175
  }
459
- searchRange *= 16;
460
- writeU16(out + 6, searchRange);
461
- writeU16(out + 8, entrySelector);
462
- writeU16(out + 10, numOutTables * 16 - searchRange);
463
176
 
464
- // write table records and data
465
- size_t dataOffset = headerSize;
466
- for (uint16_t i = 0; i < numOutTables; ++i) {
467
- auto &ot = outTables[i];
468
- uint8_t *rec = out + 12 + i * 16;
469
-
470
- writeU32(rec, ot.tag);
471
- writeU32(rec + 4, calcChecksum(ot.data.data(), ot.data.size()));
472
- writeU32(rec + 8, static_cast<uint32_t>(dataOffset));
473
- writeU32(rec + 12, static_cast<uint32_t>(ot.data.size()));
474
-
475
- memcpy(out + dataOffset, ot.data.data(), ot.data.size());
476
- dataOffset += ((ot.data.size() + 3) / 4) * 4;
477
- }
177
+ output.assign(reinterpret_cast<const uint8_t *>(resultData),
178
+ reinterpret_cast<const uint8_t *>(resultData) + length);
478
179
 
180
+ hb_blob_destroy(result);
181
+ hb_face_destroy(subset);
479
182
  return true;
480
183
  }