/src/poppler/poppler/CMap.cc
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // CMap.cc |
4 | | // |
5 | | // Copyright 2001-2003 Glyph & Cog, LLC |
6 | | // |
7 | | //======================================================================== |
8 | | |
9 | | //======================================================================== |
10 | | // |
11 | | // Modified under the Poppler project - http://poppler.freedesktop.org |
12 | | // |
13 | | // All changes made under the Poppler project to this file are licensed |
14 | | // under GPL version 2 or later |
15 | | // |
16 | | // Copyright (C) 2008 Koji Otani <sho@bbr.jp> |
17 | | // Copyright (C) 2008, 2009, 2017-2021, 2024-2026 Albert Astals Cid <aacid@kde.org> |
18 | | // Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it> |
19 | | // Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com> |
20 | | // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
21 | | // Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com> |
22 | | // Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
23 | | // Copyright (C) 2025 Arnav V <arnav0872@gmail.com> |
24 | | // Copyright (C) 2026 Stefan BrĂ¼ns <stefan.bruens@rwth-aachen.de> |
25 | | // |
26 | | // To see a description of the changes please see the Changelog file that |
27 | | // came with your tarball or type make ChangeLog if you are building from git |
28 | | // |
29 | | //======================================================================== |
30 | | |
31 | | #include <config.h> |
32 | | |
33 | | #include <cstdio> |
34 | | #include <cstdlib> |
35 | | #include <cstring> |
36 | | #include "goo/gmem.h" |
37 | | #include "goo/GooString.h" |
38 | | #include "Error.h" |
39 | | #include "GlobalParams.h" |
40 | | #include "PSTokenizer.h" |
41 | | #include "CMap.h" |
42 | | #include "Object.h" |
43 | | #include "Stream.h" |
44 | | |
45 | | //------------------------------------------------------------------------ |
46 | | |
47 | | struct CMapVectorEntry |
48 | | { |
49 | | bool isVector; |
50 | | union { |
51 | | CMapVectorEntry *vector; |
52 | | CID cid; |
53 | | }; |
54 | | }; |
55 | | |
56 | | //------------------------------------------------------------------------ |
57 | | |
58 | | static int getCharFromFile(void *data) |
59 | 0 | { |
60 | 0 | return fgetc(static_cast<FILE *>(data)); |
61 | 0 | } |
62 | | |
63 | | static int getCharFromStream(void *data) |
64 | 99.8k | { |
65 | 99.8k | return (static_cast<Stream *>(data))->getChar(); |
66 | 99.8k | } |
67 | | |
68 | | //------------------------------------------------------------------------ |
69 | | |
70 | | std::shared_ptr<CMap> CMap::parse(const std::string &collectionA, Object *obj) |
71 | 785 | { |
72 | 785 | RefRecursionChecker recursion; |
73 | 785 | return parse(collectionA, obj, recursion); |
74 | 785 | } |
75 | | |
76 | | std::shared_ptr<CMap> CMap::parse(const std::string &collectionA, Object *obj, RefRecursionChecker &recursion) |
77 | 785 | { |
78 | 785 | std::shared_ptr<CMap> cMap; |
79 | | |
80 | 785 | if (obj->isName()) { |
81 | 736 | const GooString cMapNameA(obj->getNameString()); |
82 | 736 | if (!(cMap = globalParams->getCMap(collectionA, cMapNameA.toStr()))) { |
83 | 11 | error(errSyntaxError, -1, "Unknown CMap '{0:t}' for character collection '{1:r}'", &cMapNameA, &collectionA); |
84 | 11 | } |
85 | 736 | } else if (obj->isStream()) { |
86 | 47 | if (!(cMap = CMap::parse(nullptr, collectionA, obj->getStream(), recursion))) { |
87 | 0 | error(errSyntaxError, -1, "Invalid CMap in Type 0 font"); |
88 | 0 | } |
89 | 47 | } else { |
90 | 2 | error(errSyntaxError, -1, "Invalid Encoding in Type 0 font"); |
91 | 2 | return {}; |
92 | 2 | } |
93 | 783 | return cMap; |
94 | 785 | } |
95 | | |
96 | | std::shared_ptr<CMap> CMap::parse(CMapCache *cache, const std::string &collectionA, const std::string &cMapNameA) |
97 | 393 | { |
98 | 393 | FILE *f; |
99 | | |
100 | 393 | if (!(f = globalParams->findCMapFile(collectionA, cMapNameA))) { |
101 | | |
102 | | // Check for an identity CMap. |
103 | 393 | if (cMapNameA == "Identity" || cMapNameA == "Identity-H") { |
104 | 356 | return std::shared_ptr<CMap>(new CMap(std::make_unique<GooString>(collectionA), std::make_unique<GooString>(cMapNameA), GfxFont::WritingMode::Horizontal)); |
105 | 356 | } |
106 | 37 | if (cMapNameA == "Identity-V") { |
107 | 26 | return std::shared_ptr<CMap>(new CMap(std::make_unique<GooString>(collectionA), std::make_unique<GooString>(cMapNameA), GfxFont::WritingMode::Vertical)); |
108 | 26 | } |
109 | | |
110 | 11 | error(errSyntaxError, -1, "Couldn't find '{0:r}' CMap file for '{1:r}' collection", &cMapNameA, &collectionA); |
111 | 11 | return {}; |
112 | 37 | } |
113 | | |
114 | 0 | auto cMap = std::shared_ptr<CMap>(new CMap(std::make_unique<GooString>(collectionA), std::make_unique<GooString>(cMapNameA))); |
115 | 0 | cMap->parse2(cache, &getCharFromFile, f); |
116 | |
|
117 | 0 | fclose(f); |
118 | |
|
119 | 0 | return cMap; |
120 | 393 | } |
121 | | |
122 | | std::shared_ptr<CMap> CMap::parse(CMapCache *cache, const std::string &collectionA, Stream *str, RefRecursionChecker &recursion) |
123 | 47 | { |
124 | 47 | auto cMap = std::shared_ptr<CMap>(new CMap(std::make_unique<GooString>(collectionA), nullptr)); |
125 | 47 | Ref ref; |
126 | 47 | Object obj1 = str->getDict()->lookup("UseCMap", &ref); |
127 | 47 | if (!recursion.insert(ref)) { |
128 | 0 | return {}; |
129 | 0 | } |
130 | 47 | if (!obj1.isNull()) { |
131 | 0 | cMap->useCMap(&obj1, recursion); |
132 | 0 | } |
133 | | |
134 | 47 | if (str->rewind()) { |
135 | 47 | cMap->parse2(cache, &getCharFromStream, str); |
136 | 47 | } |
137 | 47 | str->close(); |
138 | 47 | return cMap; |
139 | 47 | } |
140 | | |
141 | | void CMap::parse2(CMapCache *cache, int (*getCharFunc)(void *), void *data) |
142 | 47 | { |
143 | 47 | PSTokenizer *pst; |
144 | 47 | char tok1[256], tok2[256], tok3[256]; |
145 | 47 | int n1, n2, n3; |
146 | 47 | unsigned int start = 0, end = 0, code; |
147 | | |
148 | 47 | pst = new PSTokenizer(getCharFunc, data); |
149 | 47 | pst->getToken(tok1, sizeof(tok1), &n1); |
150 | 2.62k | while (pst->getToken(tok2, sizeof(tok2), &n2)) { |
151 | 2.57k | if (!strcmp(tok2, "usecmap")) { |
152 | 0 | if (tok1[0] == '/') { |
153 | 0 | useCMap(cache, tok1 + 1); |
154 | 0 | } |
155 | 0 | pst->getToken(tok1, sizeof(tok1), &n1); |
156 | 2.57k | } else if (!strcmp(tok1, "/WMode")) { |
157 | 37 | const int wModeI = atoi(tok2); |
158 | 37 | wMode = wModeI == 1 ? GfxFont::WritingMode::Vertical : GfxFont::WritingMode::Horizontal; |
159 | 37 | pst->getToken(tok1, sizeof(tok1), &n1); |
160 | 2.53k | } else if (!strcmp(tok2, "begincidchar")) { |
161 | 1.05k | while (pst->getToken(tok1, sizeof(tok1), &n1)) { |
162 | 1.05k | if (!strcmp(tok1, "endcidchar")) { |
163 | 29 | break; |
164 | 29 | } |
165 | 1.02k | if (!pst->getToken(tok2, sizeof(tok2), &n2) || !strcmp(tok2, "endcidchar")) { |
166 | 7 | error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap"); |
167 | 7 | break; |
168 | 7 | } |
169 | 1.01k | if (tok1[0] != '<' || tok1[n1 - 1] != '>' || n1 < 4 || (n1 & 1) != 0) { |
170 | 404 | error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap"); |
171 | 404 | continue; |
172 | 404 | } |
173 | 612 | tok1[n1 - 1] = '\0'; |
174 | 612 | if (sscanf(tok1 + 1, "%x", &code) != 1) { |
175 | 4 | error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap"); |
176 | 4 | continue; |
177 | 4 | } |
178 | 608 | n1 = (n1 - 2) / 2; |
179 | 608 | addCIDs(code, code, n1, static_cast<CID>(atoi(tok2))); |
180 | 608 | } |
181 | 41 | pst->getToken(tok1, sizeof(tok1), &n1); |
182 | 2.49k | } else if (!strcmp(tok2, "begincidrange")) { |
183 | 599 | while (pst->getToken(tok1, sizeof(tok1), &n1)) { |
184 | 598 | if (!strcmp(tok1, "endcidrange")) { |
185 | 26 | break; |
186 | 26 | } |
187 | 572 | if (!pst->getToken(tok2, sizeof(tok2), &n2) || !strcmp(tok2, "endcidrange") || !pst->getToken(tok3, sizeof(tok3), &n3) || !strcmp(tok3, "endcidrange")) { |
188 | 12 | error(errSyntaxError, -1, "Illegal entry in cidrange block in CMap"); |
189 | 12 | break; |
190 | 12 | } |
191 | 560 | if (tok1[0] == '<' && tok2[0] == '<' && n1 == n2 && n1 >= 4 && (n1 & 1) == 0) { |
192 | 399 | tok1[n1 - 1] = tok2[n1 - 1] = '\0'; |
193 | 399 | sscanf(tok1 + 1, "%x", &start); |
194 | 399 | sscanf(tok2 + 1, "%x", &end); |
195 | 399 | n1 = (n1 - 2) / 2; |
196 | 399 | addCIDs(start, end, n1, static_cast<CID>(atoi(tok3))); |
197 | 399 | } |
198 | 560 | } |
199 | 39 | pst->getToken(tok1, sizeof(tok1), &n1); |
200 | 2.45k | } else { |
201 | 2.45k | strcpy(tok1, tok2); |
202 | 2.45k | } |
203 | 2.57k | } |
204 | 47 | delete pst; |
205 | 47 | } |
206 | | |
207 | 47 | CMap::CMap(std::unique_ptr<GooString> &&collectionA, std::unique_ptr<GooString> &&cMapNameA) : collection(std::move(collectionA)), cMapName(std::move(cMapNameA)) |
208 | 47 | { |
209 | 47 | isIdent = false; |
210 | 47 | wMode = GfxFont::WritingMode::Horizontal; |
211 | 47 | vector = static_cast<CMapVectorEntry *>(gmallocn(256, sizeof(CMapVectorEntry))); |
212 | 12.0k | for (int i = 0; i < 256; ++i) { |
213 | 12.0k | vector[i].isVector = false; |
214 | 12.0k | vector[i].cid = 0; |
215 | 12.0k | } |
216 | 47 | } |
217 | | |
218 | 382 | CMap::CMap(std::unique_ptr<GooString> &&collectionA, std::unique_ptr<GooString> &&cMapNameA, GfxFont::WritingMode wModeA) : collection(std::move(collectionA)), cMapName(std::move(cMapNameA)) |
219 | 382 | { |
220 | 382 | isIdent = true; |
221 | 382 | wMode = wModeA; |
222 | 382 | vector = nullptr; |
223 | 382 | } |
224 | | |
225 | | void CMap::useCMap(CMapCache *cache, const char *useName) |
226 | 0 | { |
227 | 0 | std::shared_ptr<CMap> subCMap; |
228 | |
|
229 | 0 | const GooString useNameStr(useName); |
230 | | // if cache is non-NULL, we already have a lock, and we can use |
231 | | // CMapCache::getCMap() directly; otherwise, we need to use |
232 | | // GlobalParams::getCMap() in order to acqure the lock need to use |
233 | | // GlobalParams::getCMap |
234 | 0 | if (cache) { |
235 | 0 | subCMap = cache->getCMap(collection->toStr(), useNameStr.toStr()); |
236 | 0 | } else { |
237 | 0 | subCMap = globalParams->getCMap(collection->toStr(), useNameStr.toStr()); |
238 | 0 | } |
239 | 0 | if (!subCMap) { |
240 | 0 | return; |
241 | 0 | } |
242 | 0 | isIdent = subCMap->isIdent; |
243 | 0 | if (subCMap->vector) { |
244 | 0 | copyVector(vector, subCMap->vector); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | void CMap::useCMap(Object *obj, RefRecursionChecker &recursion) |
249 | 0 | { |
250 | 0 | std::shared_ptr<CMap> subCMap = CMap::parse(collection->toStr(), obj, recursion); |
251 | 0 | if (!subCMap) { |
252 | 0 | return; |
253 | 0 | } |
254 | 0 | isIdent = subCMap->isIdent; |
255 | 0 | if (subCMap->vector) { |
256 | 0 | copyVector(vector, subCMap->vector); |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | void CMap::copyVector(CMapVectorEntry *dest, CMapVectorEntry *src) |
261 | 0 | { |
262 | 0 | int i, j; |
263 | |
|
264 | 0 | for (i = 0; i < 256; ++i) { |
265 | 0 | if (src[i].isVector) { |
266 | 0 | if (!dest[i].isVector) { |
267 | 0 | dest[i].isVector = true; |
268 | 0 | dest[i].vector = static_cast<CMapVectorEntry *>(gmallocn(256, sizeof(CMapVectorEntry))); |
269 | 0 | for (j = 0; j < 256; ++j) { |
270 | 0 | dest[i].vector[j].isVector = false; |
271 | 0 | dest[i].vector[j].cid = 0; |
272 | 0 | } |
273 | 0 | } |
274 | 0 | copyVector(dest[i].vector, src[i].vector); |
275 | 0 | } else { |
276 | 0 | if (dest[i].isVector) { |
277 | 0 | error(errSyntaxError, -1, "Collision in usecmap"); |
278 | 0 | } else { |
279 | 0 | dest[i].cid = src[i].cid; |
280 | 0 | } |
281 | 0 | } |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | | void CMap::addCIDs(unsigned int start, unsigned int end, unsigned int nBytes, CID firstCID) |
286 | 1.00k | { |
287 | 1.00k | if (nBytes > 4) { |
288 | 8 | error(errSyntaxError, -1, "Illegal entry in cidchar block in CMap"); |
289 | 8 | return; |
290 | 8 | } |
291 | | |
292 | 999 | const unsigned int start1 = start & 0xffffff00; |
293 | 999 | const unsigned int end1 = end & 0xffffff00; |
294 | 2.04k | for (unsigned int i = start1; i <= end1; i += 0x100) { |
295 | 1.04k | CMapVectorEntry *vec = vector; |
296 | 2.09k | for (unsigned int j = nBytes - 1; j >= 1; --j) { |
297 | 1.04k | const int byte = (i >> (8 * j)) & 0xff; |
298 | 1.04k | if (!vec[byte].isVector) { |
299 | 173 | vec[byte].isVector = true; |
300 | 173 | vec[byte].vector = static_cast<CMapVectorEntry *>(gmallocn(256, sizeof(CMapVectorEntry))); |
301 | 44.4k | for (unsigned int k = 0; k < 256; ++k) { |
302 | 44.2k | vec[byte].vector[k].isVector = false; |
303 | 44.2k | vec[byte].vector[k].cid = 0; |
304 | 44.2k | } |
305 | 173 | } |
306 | 1.04k | vec = vec[byte].vector; |
307 | 1.04k | } |
308 | 1.04k | const int byte0 = (i < start) ? (start & 0xff) : 0; |
309 | 1.04k | const int byte1 = (i + 0xff > end) ? (end & 0xff) : 0xff; |
310 | 17.6k | for (int byte = byte0; byte <= byte1; ++byte) { |
311 | 16.6k | if (vec[byte].isVector) { |
312 | 0 | error(errSyntaxError, -1, "Invalid CID ({0:ux} [{1:ud} bytes]) in CMap", i, nBytes); |
313 | 16.6k | } else { |
314 | 16.6k | vec[byte].cid = firstCID + ((i + byte) - start); |
315 | 16.6k | } |
316 | 16.6k | } |
317 | 1.04k | } |
318 | 999 | } |
319 | | |
320 | | CMap::~CMap() |
321 | 429 | { |
322 | 429 | if (vector) { |
323 | 47 | freeCMapVector(vector); |
324 | 47 | } |
325 | 429 | } |
326 | | |
327 | | void CMap::freeCMapVector(CMapVectorEntry *vec) |
328 | 220 | { |
329 | 220 | int i; |
330 | | |
331 | 56.5k | for (i = 0; i < 256; ++i) { |
332 | 56.3k | if (vec[i].isVector) { |
333 | 173 | freeCMapVector(vec[i].vector); |
334 | 173 | } |
335 | 56.3k | } |
336 | 220 | gfree(vec); |
337 | 220 | } |
338 | | |
339 | | bool CMap::match(const std::string &collectionA, const std::string &cMapNameA) |
340 | 410 | { |
341 | 410 | return collection->toStr() == collectionA && cMapName->toStr() == cMapNameA; |
342 | 410 | } |
343 | | |
344 | | CID CMap::getCID(const char *s, int len, CharCode *c, int *nUsed) |
345 | 161k | { |
346 | 161k | CMapVectorEntry *vec; |
347 | 161k | CharCode cc; |
348 | 161k | int n, i; |
349 | | |
350 | 161k | vec = vector; |
351 | 161k | cc = 0; |
352 | 161k | n = 0; |
353 | 161k | while (vec && n < len) { |
354 | 12.7k | i = s[n++] & 0xff; |
355 | 12.7k | cc = (cc << 8) | i; |
356 | 12.7k | if (!vec[i].isVector) { |
357 | 12.4k | *c = cc; |
358 | 12.4k | *nUsed = n; |
359 | 12.4k | return vec[i].cid; |
360 | 12.4k | } |
361 | 336 | vec = vec[i].vector; |
362 | 336 | } |
363 | 149k | if (isIdent && len >= 2) { |
364 | | // identity CMap |
365 | 148k | *nUsed = 2; |
366 | 148k | *c = cc = ((s[0] & 0xff) << 8) + (s[1] & 0xff); |
367 | 148k | return cc; |
368 | 148k | } |
369 | 395 | *nUsed = 1; |
370 | 395 | *c = s[0] & 0xff; |
371 | 395 | return 0; |
372 | 149k | } |
373 | | |
374 | | void CMap::setReverseMapVector(unsigned int startCode, CMapVectorEntry *vec, unsigned int *rmap, unsigned int rmapSize, unsigned int ncand) |
375 | 0 | { |
376 | 0 | int i; |
377 | |
|
378 | 0 | if (vec == nullptr) { |
379 | 0 | return; |
380 | 0 | } |
381 | 0 | for (i = 0; i < 256; i++) { |
382 | 0 | if (vec[i].isVector) { |
383 | 0 | setReverseMapVector((startCode + i) << 8, vec[i].vector, rmap, rmapSize, ncand); |
384 | 0 | } else { |
385 | 0 | unsigned int cid = vec[i].cid; |
386 | |
|
387 | 0 | if (cid < rmapSize) { |
388 | 0 | unsigned int cand; |
389 | |
|
390 | 0 | for (cand = 0; cand < ncand; cand++) { |
391 | 0 | unsigned int code = startCode + i; |
392 | 0 | unsigned int idx = cid * ncand + cand; |
393 | 0 | if (rmap[idx] == 0) { |
394 | 0 | rmap[idx] = code; |
395 | 0 | break; |
396 | 0 | } |
397 | 0 | if (rmap[idx] == code) { |
398 | 0 | break; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | } |
402 | 0 | } |
403 | 0 | } |
404 | 0 | } |
405 | | |
406 | | void CMap::setReverseMap(unsigned int *rmap, unsigned int rmapSize, unsigned int ncand) |
407 | 0 | { |
408 | 0 | setReverseMapVector(0, vector, rmap, rmapSize, ncand); |
409 | 0 | } |
410 | | |
411 | | //------------------------------------------------------------------------ |
412 | | |
413 | 19.6k | CMapCache::CMapCache() = default; |
414 | | |
415 | | std::shared_ptr<CMap> CMapCache::getCMap(const std::string &collection, const std::string &cMapName) |
416 | 736 | { |
417 | 736 | int i, j; |
418 | | |
419 | 736 | if (cache[0] && cache[0]->match(collection, cMapName)) { |
420 | 339 | return cache[0]; |
421 | 339 | } |
422 | 1.57k | for (i = 1; i < cMapCacheSize; ++i) { |
423 | 1.18k | if (cache[i] && cache[i]->match(collection, cMapName)) { |
424 | 4 | std::shared_ptr<CMap> cmap = cache[i]; |
425 | 8 | for (j = i; j >= 1; --j) { |
426 | 4 | cache[j] = cache[j - 1]; |
427 | 4 | } |
428 | 4 | cache[0] = cmap; |
429 | 4 | return cmap; |
430 | 4 | } |
431 | 1.18k | } |
432 | 393 | std::shared_ptr<CMap> cmap = CMap::parse(this, collection, cMapName); |
433 | 393 | if (cmap) { |
434 | 1.52k | for (j = cMapCacheSize - 1; j >= 1; --j) { |
435 | 1.14k | cache[j] = cache[j - 1]; |
436 | 1.14k | } |
437 | 382 | cache[0] = cmap; |
438 | 382 | return cmap; |
439 | 382 | } |
440 | 11 | return {}; |
441 | 393 | } |