qpdf-compress 0.2.0 → 0.4.1
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.
- package/CHANGELOG.md +124 -0
- package/README.md +87 -57
- package/binding.gyp +3 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -14
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -9
- package/dist/types.d.ts.map +1 -1
- package/lib/index.ts +12 -21
- package/lib/types.ts +3 -9
- package/package.json +5 -2
- package/src/font_subset.cc +480 -0
- package/src/font_subset.h +17 -0
- package/src/images.cc +473 -126
- package/src/images.h +6 -6
- package/src/optimize.cc +1048 -0
- package/src/optimize.h +15 -0
- package/src/qpdf_addon.cc +34 -44
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#include "font_subset.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <map>
|
|
6
|
+
#include <queue>
|
|
7
|
+
|
|
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
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Table directory parsing
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
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;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// cmap parsing — extract character code → glyph ID mapping
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
std::set<uint16_t> mapCodesToGlyphIds(const uint8_t *data, size_t size,
|
|
85
|
+
const std::set<uint16_t> &charCodes) {
|
|
86
|
+
std::set<uint16_t> glyphIds;
|
|
87
|
+
glyphIds.insert(0); // always keep .notdef
|
|
88
|
+
|
|
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)
|
|
100
|
+
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
|
+
}
|
|
120
|
+
|
|
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
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return glyphIds;
|
|
193
|
+
}
|
|
194
|
+
|
|
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
|
+
}
|
|
247
|
+
|
|
248
|
+
// skip arguments based on flags
|
|
249
|
+
if (flags & 0x0001) // ARG_1_AND_2_ARE_WORDS
|
|
250
|
+
pos += 4;
|
|
251
|
+
else
|
|
252
|
+
pos += 2;
|
|
253
|
+
|
|
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;
|
|
260
|
+
|
|
261
|
+
if (!(flags & 0x0020)) // MORE_COMPONENTS
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// TrueType font subsetting — keep only used glyphs
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
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;
|
|
283
|
+
}
|
|
284
|
+
|
|
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())
|
|
300
|
+
return false;
|
|
301
|
+
|
|
302
|
+
const uint8_t *headData = data + headIt->second.offset;
|
|
303
|
+
if (headIt->second.length < 54)
|
|
304
|
+
return false;
|
|
305
|
+
|
|
306
|
+
bool locaLong = readI16(headData + 50) == 1;
|
|
307
|
+
|
|
308
|
+
const uint8_t *maxpData = data + maxpIt->second.offset;
|
|
309
|
+
if (maxpIt->second.length < 6)
|
|
310
|
+
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
|
+
}
|
|
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
|
+
|
|
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
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
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));
|
|
431
|
+
}
|
|
432
|
+
|
|
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; });
|
|
437
|
+
|
|
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
|
|
444
|
+
|
|
445
|
+
output.resize(totalSize, 0);
|
|
446
|
+
uint8_t *out = output.data();
|
|
447
|
+
|
|
448
|
+
// write offset table
|
|
449
|
+
writeU32(out, readU32(data)); // sfVersion (same as original)
|
|
450
|
+
writeU16(out + 4, numOutTables);
|
|
451
|
+
|
|
452
|
+
// searchRange, entrySelector, rangeShift
|
|
453
|
+
uint16_t searchRange = 1;
|
|
454
|
+
uint16_t entrySelector = 0;
|
|
455
|
+
while (searchRange * 2 <= numOutTables) {
|
|
456
|
+
searchRange *= 2;
|
|
457
|
+
++entrySelector;
|
|
458
|
+
}
|
|
459
|
+
searchRange *= 16;
|
|
460
|
+
writeU16(out + 6, searchRange);
|
|
461
|
+
writeU16(out + 8, entrySelector);
|
|
462
|
+
writeU16(out + 10, numOutTables * 16 - searchRange);
|
|
463
|
+
|
|
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
|
+
}
|
|
478
|
+
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <set>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
// subset a TrueType font binary (TTF), keeping only the specified glyph IDs.
|
|
9
|
+
// glyph 0 (.notdef) is always retained. returns true on success.
|
|
10
|
+
bool subsetTrueTypeFont(const uint8_t *data, size_t size,
|
|
11
|
+
const std::set<uint16_t> &usedGlyphIds,
|
|
12
|
+
std::vector<uint8_t> &output);
|
|
13
|
+
|
|
14
|
+
// map character codes to glyph IDs using the font's cmap table.
|
|
15
|
+
// returns a set of glyph IDs that correspond to the given character codes.
|
|
16
|
+
std::set<uint16_t> mapCodesToGlyphIds(const uint8_t *data, size_t size,
|
|
17
|
+
const std::set<uint16_t> &charCodes);
|