Coverage Report

Created: 2026-08-25 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/poppler/UTF.cc
Line
Count
Source
1
//========================================================================
2
//
3
// UTF.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) 2012, 2017, 2021, 2023, 2024 Adrian Johnson <ajohnson@redneon.com>
18
// Copyright (C) 2012 Hib Eris <hib@hiberis.nl>
19
// Copyright (C) 2016, 2018-2022, 2024, 2025 Albert Astals Cid <aacid@kde.org>
20
// Copyright (C) 2016 Jason Crain <jason@aquaticape.us>
21
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
22
// Copyright (C) 2018, 2020 Nelson Benítez León <nbenitezl@gmail.com>
23
// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
24
// Copyright (C) 2023-2025 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
25
// Copyright (C) 2023 Even Rouault <even.rouault@spatialys.com>
26
// Copyright (C) 2023, 2024 Oliver Sander <oliver.sander@tu-dresden.de>
27
// Copyright (C) 2025 Jonathan Hähne <jonathan.haehne@hotmail.com>
28
//
29
// To see a description of the changes please see the Changelog file that
30
// came with your tarball or type make ChangeLog if you are building from git
31
//
32
//========================================================================
33
34
#include "goo/gmem.h"
35
#include "PDFDocEncoding.h"
36
#include "GlobalParams.h"
37
#include "UnicodeMap.h"
38
#include "UTF.h"
39
#include "UnicodeMapFuncs.h"
40
#include <algorithm>
41
42
#include <config.h>
43
44
std::vector<Unicode> UTF16toUCS4(std::span<const Unicode> utf16)
45
1.97k
{
46
    // count characters
47
1.97k
    int len = 0;
48
4.64k
    for (size_t i = 0; i < utf16.size(); i++) {
49
2.66k
        if (utf16[i] >= 0xd800 && utf16[i] < 0xdc00 && i + 1 < utf16.size() && utf16[i + 1] >= 0xdc00 && utf16[i + 1] < 0xe000) {
50
0
            i++; /* surrogate pair */
51
0
        }
52
2.66k
        len++;
53
2.66k
    }
54
1.97k
    std::vector<Unicode> u;
55
1.97k
    u.reserve(len);
56
    // convert string
57
4.64k
    for (size_t i = 0; i < utf16.size(); i++) {
58
2.66k
        if (utf16[i] >= 0xd800 && utf16[i] < 0xdc00) { /* surrogate pair */
59
5
            if (i + 1 < utf16.size() && utf16[i + 1] >= 0xdc00 && utf16[i + 1] < 0xe000) {
60
                /* next code is a low surrogate */
61
0
                u.push_back((((utf16[i] & 0x3ff) << 10) | (utf16[i + 1] & 0x3ff)) + 0x10000);
62
0
                ++i;
63
5
            } else {
64
                /* missing low surrogate
65
                   replace it with REPLACEMENT CHARACTER (U+FFFD) */
66
5
                u.push_back(0xfffd);
67
5
            }
68
2.66k
        } else if (utf16[i] >= 0xdc00 && utf16[i] < 0xe000) {
69
            /* invalid low surrogate
70
               replace it with REPLACEMENT CHARACTER (U+FFFD) */
71
11
            u.push_back(0xfffd);
72
2.65k
        } else {
73
2.65k
            u.push_back(utf16[i]);
74
2.65k
        }
75
2.66k
        if (!UnicodeIsValid(u.back())) {
76
0
            u.back() = 0xfffd;
77
0
        }
78
2.66k
    }
79
1.97k
    return u;
80
1.97k
}
81
82
std::vector<Unicode> TextStringToUCS4(std::string_view textStr)
83
4.82k
{
84
4.82k
    bool isUnicode, isUnicodeLE;
85
86
4.82k
    int len = textStr.size();
87
4.82k
    if (len == 0) {
88
878
        return {};
89
878
    }
90
91
3.94k
    if (hasUnicodeByteOrderMark(textStr)) {
92
0
        isUnicode = true;
93
0
        isUnicodeLE = false;
94
3.94k
    } else if (hasUnicodeByteOrderMarkLE(textStr)) {
95
0
        isUnicode = false;
96
0
        isUnicodeLE = true;
97
3.94k
    } else {
98
3.94k
        isUnicode = false;
99
3.94k
        isUnicodeLE = false;
100
3.94k
    }
101
102
3.94k
    if (isUnicode || isUnicodeLE) {
103
0
        len = len / 2 - 1;
104
0
        if (len > 0) {
105
0
            std::vector<Unicode> utf16;
106
0
            utf16.reserve(len);
107
0
            for (int i = 0; i < len; i++) {
108
0
                if (isUnicode) {
109
0
                    utf16.push_back((textStr[2 + i * 2] & 0xff) << 8 | (textStr[3 + i * 2] & 0xff));
110
0
                } else { // UnicodeLE
111
0
                    utf16.push_back((textStr[3 + i * 2] & 0xff) << 8 | (textStr[2 + i * 2] & 0xff));
112
0
                }
113
0
            }
114
0
            return UTF16toUCS4(utf16);
115
0
        }
116
0
        return {};
117
0
    }
118
3.94k
    std::vector<Unicode> u;
119
3.94k
    u.reserve(len);
120
232k
    for (int i = 0; i < len; i++) {
121
228k
        u.push_back(pdfDocEncoding[textStr[i] & 0xff]);
122
228k
    }
123
3.94k
    return u;
124
3.94k
}
125
126
bool UnicodeIsWhitespace(Unicode ucs4)
127
1.13M
{
128
1.13M
    static Unicode const spaces[] = { 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x0020, 0x0085, 0x00A0, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F, 0x205F, 0x3000 };
129
1.13M
    Unicode const *end = spaces + sizeof(spaces) / sizeof(spaces[0]);
130
1.13M
    Unicode const *i = std::lower_bound(spaces, end, ucs4);
131
1.13M
    return (i != end && *i == ucs4);
132
1.13M
}
133
134
//
135
// decodeUtf8() and decodeUtf8Table are:
136
//
137
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
138
//
139
// Permission is hereby granted, free of charge, to any person
140
// obtaining a copy of this software and associated documentation
141
// files (the "Software"), to deal in the Software without
142
// restriction, including without limitation the rights to use, copy,
143
// modify, merge, publish, distribute, sublicense, and/or sell copies
144
// of the Software, and to permit persons to whom the Software is
145
// furnished to do so, subject to the following conditions:
146
147
// The above copyright notice and this permission notice shall be
148
// included in all copies or substantial portions of the Software.
149
//
150
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
151
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
152
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
153
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
154
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
155
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
156
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
157
// SOFTWARE.
158
//
159
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
160
//
161
static const uint32_t UTF8_ACCEPT = 0;
162
static const uint32_t UTF8_REJECT = 12;
163
static const uint32_t UCS4_MAX = 0x10FFFF;
164
static const Unicode REPLACEMENT_CHAR = 0xFFFD;
165
166
// clang-format off
167
static const uint8_t decodeUtf8Table[] = {
168
  // The first part of the table maps bytes to character classes
169
  // to reduce the size of the transition table and create bitmasks.
170
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
171
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
172
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
173
   0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
174
   1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
175
   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
176
   8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
177
  10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, // e0..ff
178
179
  // The second part is a transition table that maps a combination
180
  // of a state of the automaton and a character class to a state.
181
   0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
182
  12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
183
  12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
184
  12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
185
  12,36,12,12,12,12,12,12,12,12,12,12,
186
};
187
// clang-format on
188
189
// Decode utf8 state machine for fast UTF-8 decoding. Initialise state
190
// to 0 and call decodeUtf8() for each byte of UTF-8. Return value
191
// (and state) is UTF8_ACCEPT when it has found a valid codepoint
192
// (codepoint returned in codep), UTF8_REJECT when the byte is not
193
// allowed to occur at its position, and some other positive value if
194
// more bytes have to be read.  Reset state to 0 to recover from
195
// errors.
196
inline uint32_t decodeUtf8(uint32_t *state, uint32_t *codep, char byte)
197
736k
{
198
736k
    uint32_t b = static_cast<unsigned char>(byte);
199
736k
    uint32_t type = decodeUtf8Table[b];
200
201
736k
    *codep = (*state != UTF8_ACCEPT) ? (b & 0x3fU) | (*codep << 6) : (0xff >> type) & b;
202
203
736k
    *state = decodeUtf8Table[256 + *state + type];
204
736k
    return *state;
205
736k
}
206
207
std::vector<Unicode> utf8ToUCS4(std::string_view utf8)
208
0
{
209
0
    uint32_t codepoint;
210
0
    uint32_t state = 0;
211
212
0
    std::vector<Unicode> u;
213
214
0
    for (auto c : utf8) {
215
0
        decodeUtf8(&state, &codepoint, c);
216
0
        if (state == UTF8_ACCEPT) {
217
0
            u.push_back(codepoint);
218
0
        } else if (state == UTF8_REJECT) {
219
0
            u.push_back(REPLACEMENT_CHAR); // invalid byte for this position
220
0
            state = 0;
221
0
        }
222
0
    }
223
0
    if (state != UTF8_ACCEPT && state != UTF8_REJECT) {
224
0
        u.push_back(REPLACEMENT_CHAR); // invalid byte for this position
225
0
    }
226
0
    u.shrink_to_fit();
227
228
0
    return u;
229
0
}
230
231
// Count number of UTF-16 code units required to convert a UTF-8 string
232
// (excluding terminating NULL). Each invalid byte is counted as a
233
// code point since the UTF-8 conversion functions will replace it with
234
// REPLACEMENT_CHAR.
235
int utf8CountUtf16CodeUnits(std::string_view utf8)
236
0
{
237
0
    uint32_t codepoint;
238
0
    uint32_t state = 0;
239
0
    int count = 0;
240
241
0
    for (auto c : utf8) {
242
0
        decodeUtf8(&state, &codepoint, c);
243
0
        if (state == UTF8_ACCEPT) {
244
0
            if (codepoint < 0x10000) {
245
0
                count++;
246
0
            } else if (codepoint <= UCS4_MAX) {
247
0
                count += 2;
248
0
            } else {
249
0
                count++; // replace with REPLACEMENT_CHAR
250
0
            }
251
0
        } else if (state == UTF8_REJECT) {
252
0
            count++; // replace with REPLACEMENT_CHAR
253
0
            state = 0;
254
0
        }
255
0
    }
256
0
    if (state != UTF8_ACCEPT && state != UTF8_REJECT) {
257
0
        count++; // replace with REPLACEMENT_CHAR
258
0
    }
259
260
0
    return count;
261
0
}
262
263
std::u16string utf8ToUtf16(std::string_view utf8)
264
409
{
265
409
    uint32_t codepoint;
266
409
    uint32_t state = 0;
267
409
    if (isUtf8WithBom(utf8)) {
268
409
        utf8 = utf8.substr(3);
269
409
    }
270
409
    std::u16string utf16;
271
736k
    for (auto c : utf8) {
272
736k
        decodeUtf8(&state, &codepoint, c);
273
736k
        if (state == UTF8_ACCEPT) {
274
353k
            if (codepoint < 0x10000) {
275
353k
                utf16.push_back(static_cast<uint16_t>(codepoint));
276
353k
            } else if (codepoint <= UCS4_MAX) {
277
1
                utf16.push_back(static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)));
278
1
                utf16.push_back(static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
279
1
            } else {
280
0
                utf16.push_back(REPLACEMENT_CHAR);
281
0
                state = 0;
282
0
            }
283
383k
        } else if (state == UTF8_REJECT) {
284
382k
            utf16.push_back(REPLACEMENT_CHAR); // invalid byte for this position
285
382k
        }
286
736k
    }
287
    // replace any trailing bytes too short for a valid UTF-8 with a replacement char
288
409
    if (state != UTF8_ACCEPT && state != UTF8_REJECT) {
289
57
        utf16.push_back(REPLACEMENT_CHAR);
290
57
    }
291
409
    return utf16;
292
409
}
293
294
std::string utf8ToUtf16WithBom(std::string_view utf8)
295
409
{
296
409
    if (utf8.empty()) {
297
0
        return {};
298
0
    }
299
409
    std::u16string utf16 = utf8ToUtf16(utf8);
300
409
    char *tmp_str = reinterpret_cast<char *>(utf16.data());
301
409
#if !WORDS_BIGENDIAN
302
736k
    for (size_t i = 0; i < utf16.size(); i++) {
303
736k
        std::swap(tmp_str[i * 2], tmp_str[i * 2 + 1]);
304
736k
    }
305
409
#endif
306
307
409
    std::string result(unicodeByteOrderMark);
308
409
    result.append(tmp_str, utf16.size() * 2);
309
409
    return result;
310
409
}
311
312
static const uint32_t UTF16_ACCEPT = 0;
313
static const uint32_t UTF16_REJECT = -1;
314
315
// Initialise state to 0. Returns UTF16_ACCEPT when a valid code point
316
// has been found, UTF16_REJECT when invalid code unit for this state,
317
// some other valid if another code unit needs to be read.
318
inline uint32_t decodeUtf16(uint32_t *state, uint32_t *codePoint, uint16_t codeUnit)
319
0
{
320
0
    if (*state == 0) {
321
0
        if (codeUnit >= 0xd800 && codeUnit < 0xdc00) { /* surrogate pair */
322
0
            *state = codeUnit;
323
0
            return *state;
324
0
        }
325
0
        if (codeUnit >= 0xdc00 && codeUnit < 0xe000) {
326
            /* invalid low surrogate */
327
0
            return UTF16_REJECT;
328
0
        }
329
0
        *codePoint = codeUnit;
330
0
        return UTF16_ACCEPT;
331
0
    }
332
0
    if (codeUnit >= 0xdc00 && codeUnit < 0xe000) {
333
0
        *codePoint = (((*state & 0x3ff) << 10) | (codeUnit & 0x3ff)) + 0x10000;
334
0
        *state = 0;
335
0
        return UTF16_ACCEPT;
336
0
    }
337
    /* invalid high surrogate */
338
0
    return UTF16_REJECT;
339
0
}
340
341
// Count number of UTF-8 bytes required to convert a UTF-16 string to
342
// UTF-8 (excluding terminating NULL).
343
int utf16CountUtf8Bytes(const uint16_t *utf16)
344
0
{
345
0
    uint32_t codepoint = 0;
346
0
    uint32_t state = 0;
347
0
    int count = 0;
348
349
0
    while (*utf16) {
350
0
        decodeUtf16(&state, &codepoint, *utf16);
351
0
        if (state == UTF16_ACCEPT) {
352
0
            if (codepoint < 0x80) {
353
0
                count++;
354
0
            } else if (codepoint < 0x800) {
355
0
                count += 2;
356
0
            } else if (codepoint < 0x10000) {
357
0
                count += 3;
358
0
            } else if (codepoint <= UCS4_MAX) {
359
0
                count += 4;
360
0
            } else {
361
0
                count += 3; // replace with REPLACEMENT_CHAR
362
0
            }
363
0
        } else if (state == UTF16_REJECT) {
364
0
            count += 3; // replace with REPLACEMENT_CHAR
365
0
            state = 0;
366
0
        }
367
0
        utf16++;
368
0
    }
369
0
    if (state != UTF8_ACCEPT && state != UTF8_REJECT) {
370
0
        count += 3; // replace with REPLACEMENT_CHAR
371
0
    }
372
373
0
    return count;
374
0
}
375
376
std::string utf16ToUtf8(const uint16_t *utf16, int maxUtf16)
377
0
{
378
0
    uint32_t codepoint = 0;
379
0
    uint32_t state = 0;
380
0
    int nIn = 0;
381
0
    char p[4];
382
0
    std::string utf8;
383
0
    while (*utf16 && nIn < maxUtf16) {
384
0
        decodeUtf16(&state, &codepoint, *utf16);
385
0
        if (state == UTF16_ACCEPT || state == UTF16_REJECT) {
386
0
            if (state == UTF16_REJECT || codepoint > UCS4_MAX) {
387
0
                codepoint = REPLACEMENT_CHAR;
388
0
                state = 0;
389
0
            }
390
391
0
            int count = mapUTF8(codepoint, p, 4);
392
0
            utf8.append(std::string_view(p, count));
393
0
        }
394
0
        utf16++;
395
0
        nIn++;
396
0
    }
397
    // replace any trailing bytes too short for a valid UTF-8 with a replacement char
398
0
    if (state != UTF16_ACCEPT && state != UTF16_REJECT) {
399
0
        int count = mapUTF8(REPLACEMENT_CHAR, p, 4);
400
0
        utf8.append(std::string_view(p, count));
401
0
    }
402
0
    return utf8;
403
0
}
404
405
void unicodeToAscii7(std::span<const Unicode> in, Unicode **ucs4_out, int *out_len, const int *in_idx, int **indices)
406
0
{
407
0
    const UnicodeMap *uMap = globalParams->getUnicodeMap("ASCII7");
408
0
    int *idx = nullptr;
409
410
0
    if (in.empty()) {
411
0
        *ucs4_out = nullptr;
412
0
        *out_len = 0;
413
0
        return;
414
0
    }
415
416
0
    if (indices) {
417
0
        if (!in_idx) {
418
0
            indices = nullptr;
419
0
        } else {
420
0
            idx = static_cast<int *>(gmallocn(in.size() * 8 + 1, sizeof(int)));
421
0
        }
422
0
    }
423
424
0
    std::string str;
425
426
0
    char buf[8]; // 8 is enough for mapping an unicode char to a string
427
0
    size_t i;
428
0
    int n, k;
429
430
0
    for (i = k = 0; i < in.size(); ++i) {
431
0
        n = uMap->mapUnicode(in[i], buf, sizeof(buf));
432
0
        if (!n) {
433
            // the Unicode char could not be converted to ascii7 counterpart
434
            // so just fill with a non-printable ascii char
435
0
            buf[0] = 31;
436
0
            n = 1;
437
0
        }
438
0
        str.append(buf, n);
439
0
        if (indices) {
440
0
            for (; n > 0; n--) {
441
0
                idx[k++] = in_idx[i];
442
0
            }
443
0
        }
444
0
    }
445
446
0
    std::vector<Unicode> ucs4 = TextStringToUCS4(str);
447
0
    *out_len = ucs4.size();
448
0
    *ucs4_out = static_cast<Unicode *>(gmallocn(ucs4.size(), sizeof(Unicode)));
449
0
    memcpy(*ucs4_out, ucs4.data(), ucs4.size() * sizeof(Unicode));
450
451
0
    if (indices) {
452
0
        idx[k] = in_idx[in.size()];
453
0
        *indices = idx;
454
0
    }
455
0
}
456
457
// Convert a PDF Text String to UTF-8
458
//   textStr    - PDF text string
459
//   returns UTF-8 string.
460
std::string TextStringToUtf8(std::string_view textStr)
461
0
{
462
0
    int i, len;
463
0
    std::string utf8;
464
465
0
    len = textStr.size();
466
0
    if (hasUnicodeByteOrderMark(textStr)) {
467
0
        std::vector<uint16_t> utf16;
468
0
        len = len / 2 - 1;
469
0
        utf16.resize(len + 1);
470
0
        for (i = 0; i < len; i++) {
471
0
            utf16[i] = (textStr[2 + i * 2] & 0xff) << 8 | (textStr[3 + i * 2] & 0xff);
472
0
        }
473
0
        utf16[i] = 0;
474
0
        utf8 = utf16ToUtf8(utf16.data(), utf16.size());
475
0
    } else {
476
0
        utf8.resize(len + 1);
477
0
        for (i = 0; i < len; i++) {
478
0
            utf8[i] = pdfDocEncoding[textStr[i] & 0xff];
479
0
        }
480
0
        utf8[i] = 0;
481
0
    }
482
0
    return utf8;
483
0
}