Coverage Report

Created: 2026-07-16 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDF_String.cc
Line
Count
Source
1
#include <qpdf/QPDFObject_private.hh>
2
3
#include <qpdf/QPDFObjectHandle_private.hh>
4
#include <qpdf/QUtil.hh>
5
#include <qpdf/Util.hh>
6
7
// DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of
8
// including it in case it may accidentally be used.
9
10
static bool
11
is_iso_latin1_printable(char ch)
12
8.30M
{
13
8.30M
    return (ch >= 32 && ch <= 126) || static_cast<unsigned char>(ch) >= 160;
14
8.30M
}
15
16
void
17
QPDF_String::writeJSON(int json_version, JSON::Writer& p)
18
26.8k
{
19
26.8k
    if (json_version == 1) {
20
0
        if (util::is_utf16(val)) {
21
0
            p << "\"" << JSON::Writer::encode_string(QUtil::utf16_to_utf8(val)) << "\"";
22
0
            return;
23
0
        }
24
0
        if (util::is_explicit_utf8(val)) {
25
            // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte
26
            // representation of U+FEFF.
27
0
            p << "\"" << JSON::Writer::encode_string(val.substr(3)) << "\"";
28
0
            return;
29
0
        }
30
0
        p << "\"" << JSON::Writer::encode_string(QUtil::pdf_doc_to_utf8(val)) << "\"";
31
0
        return;
32
0
    }
33
    // See if we can unambiguously represent as Unicode.
34
26.8k
    if (util::is_utf16(val)) {
35
1.66k
        p << "\"u:" << JSON::Writer::encode_string(QUtil::utf16_to_utf8(val)) << "\"";
36
1.66k
        return;
37
1.66k
    }
38
    // See if we can unambiguously represent as Unicode.
39
25.1k
    if (util::is_explicit_utf8(val)) {
40
1.24k
        p << "\"u:" << JSON::Writer::encode_string(val.substr(3)) << "\"";
41
1.24k
        return;
42
1.24k
    }
43
23.8k
    if (!useHexString()) {
44
10.4k
        auto candidate = QUtil::pdf_doc_to_utf8(val);
45
10.4k
        std::string test;
46
10.4k
        if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && test == val) {
47
            // This is a PDF-doc string that can be losslessly encoded as Unicode.
48
10.1k
            p << "\"u:" << JSON::Writer::encode_string(candidate) << "\"";
49
10.1k
            return;
50
10.1k
        }
51
10.4k
    }
52
13.7k
    p << "\"b:" << QUtil::hex_encode(val) << "\"";
53
13.7k
}
54
55
bool
56
QPDF_String::useHexString() const
57
152k
{
58
    // Heuristic: use the hexadecimal representation of a string if there are any non-printable (in
59
    // PDF Doc encoding) characters or if too large of a proportion of the string consists of
60
    // non-ASCII characters.
61
152k
    unsigned int non_ascii = 0;
62
58.1M
    for (auto const ch: val) {
63
58.1M
        if (ch > 126) {
64
220k
            ++non_ascii;
65
57.9M
        } else if (ch >= 32) {
66
9.23M
            continue;
67
48.6M
        } else if (ch < 0 || ch >= 24) {
68
4.02M
            ++non_ascii;
69
44.6M
        } else if (!(ch == '\n' || ch == '\r' || ch == '\t' || ch == '\b' || ch == '\f')) {
70
45.1k
            return true;
71
45.1k
        }
72
58.1M
    }
73
107k
    return 5 * non_ascii > val.length();
74
152k
}
75
76
std::string
77
QPDF_String::unparse(bool force_binary)
78
128k
{
79
128k
    bool use_hexstring = force_binary || useHexString();
80
128k
    std::string result;
81
128k
    if (use_hexstring) {
82
55.2k
        static auto constexpr hexchars = "0123456789abcdef";
83
55.2k
        result.reserve(2 * val.length() + 2);
84
55.2k
        result += '<';
85
28.1M
        for (const char c: val) {
86
28.1M
            result += hexchars[static_cast<unsigned char>(c) >> 4];
87
28.1M
            result += hexchars[c & 0x0f];
88
28.1M
        }
89
55.2k
        result += '>';
90
73.4k
    } else {
91
73.4k
        result += "(";
92
44.5M
        for (unsigned int i = 0; i < val.length(); ++i) {
93
44.5M
            char ch = val.at(i);
94
44.5M
            switch (ch) {
95
322k
            case '\n':
96
322k
                result += "\\n";
97
322k
                break;
98
99
5.22k
            case '\r':
100
5.22k
                result += "\\r";
101
5.22k
                break;
102
103
35.6M
            case '\t':
104
35.6M
                result += "\\t";
105
35.6M
                break;
106
107
56.2k
            case '\b':
108
56.2k
                result += "\\b";
109
56.2k
                break;
110
111
123k
            case '\f':
112
123k
                result += "\\f";
113
123k
                break;
114
115
16.3k
            case '(':
116
16.3k
                result += "\\(";
117
16.3k
                break;
118
119
16.4k
            case ')':
120
16.4k
                result += "\\)";
121
16.4k
                break;
122
123
14.6k
            case '\\':
124
14.6k
                result += "\\\\";
125
14.6k
                break;
126
127
8.30M
            default:
128
8.30M
                if (is_iso_latin1_printable(ch)) {
129
6.49M
                    result += val.at(i);
130
6.49M
                } else {
131
1.81M
                    result += "\\" +
132
1.81M
                        QUtil::int_to_string_base(
133
1.81M
                                  static_cast<int>(static_cast<unsigned char>(ch)), 8, 3);
134
1.81M
                }
135
8.30M
                break;
136
44.5M
            }
137
44.5M
        }
138
73.4k
        result += ")";
139
73.4k
    }
140
141
128k
    return result;
142
128k
}