Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}