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.
- package/CHANGELOG.md +15 -0
- package/binding.gyp +24 -9
- package/package.json +3 -2
- package/scripts/download-harfbuzz.mjs +166 -0
- package/scripts/download-mozjpeg.mjs +1 -0
- package/scripts/download-qpdf.mjs +8 -1
- package/scripts/install.mjs +1 -0
- package/src/content.cc +291 -0
- package/src/content.h +6 -0
- package/src/encoding_tables.h +42 -0
- package/src/font_subset.cc +128 -425
- package/src/font_subset.h +17 -7
- package/src/fonts.cc +1209 -0
- package/src/fonts.h +5 -0
- package/src/hash_utils.h +15 -0
- package/src/images.cc +156 -132
- package/src/images.h +20 -2
- package/src/jpeg.cc +6 -0
- package/src/jpeg.h +1 -0
- package/src/optimize.h +5 -13
- package/src/qpdf_addon.cc +137 -55
- package/src/strip.cc +391 -0
- package/src/strip.h +9 -0
- package/src/structure.cc +346 -0
- package/src/structure.h +7 -0
- package/src/optimize.cc +0 -1048
|
@@ -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
|
+
}
|
package/src/font_subset.cc
CHANGED
|
@@ -1,480 +1,183 @@
|
|
|
1
1
|
#include "font_subset.h"
|
|
2
2
|
|
|
3
|
-
#include <algorithm>
|
|
4
3
|
#include <cstring>
|
|
5
|
-
#include <
|
|
6
|
-
#include <
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
//
|
|
11
|
+
// Glyph lookup using HarfBuzz — replaces manual cmap parsing
|
|
40
12
|
// ---------------------------------------------------------------------------
|
|
41
13
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
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
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
//
|
|
88
|
+
// Font subsetting using HarfBuzz hb-subset (TrueType and CFF/OpenType)
|
|
269
89
|
// ---------------------------------------------------------------------------
|
|
270
90
|
|
|
271
|
-
static uint32_t
|
|
272
|
-
uint32_t
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
303
|
-
|
|
104
|
+
uint32_t sfVersion = readU32(data);
|
|
105
|
+
if (sfVersion != 0x00010000 && sfVersion != 0x74727565 &&
|
|
106
|
+
sfVersion != 0x4F54544F) // OTTO
|
|
304
107
|
return false;
|
|
305
108
|
|
|
306
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
446
|
-
|
|
164
|
+
if (!subset)
|
|
165
|
+
return false;
|
|
447
166
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
465
|
-
|
|
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
|
}
|