Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pypdf/generic/_utils.py: 48%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import codecs
2from typing import Union
4from .._codecs import _pdfdoc_encoding
5from .._utils import StreamType, logger_warning, read_non_whitespace
6from ..errors import STREAM_TRUNCATED_PREMATURELY, PdfStreamError
7from ._base import ByteStringObject, TextStringObject
10def hex_to_rgb(value: str) -> tuple[float, float, float]:
11 return tuple(int(value.lstrip("#")[i : i + 2], 16) / 255.0 for i in (0, 2, 4)) # type: ignore[return-value]
14def read_hex_string_from_stream(
15 stream: StreamType,
16 forced_encoding: Union[None, str, list[str], dict[int, str]] = None,
17) -> Union["TextStringObject", "ByteStringObject"]:
18 stream.read(1)
19 arr = []
20 x = b""
21 while True:
22 tok = read_non_whitespace(stream)
23 if not tok:
24 raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
25 if tok == b">":
26 break
27 if tok not in b"0123456789abcdefABCDEF":
28 raise PdfStreamError(
29 f"Invalid hexadecimal character {tok!r} in hex string"
30 )
31 x += tok
32 if len(x) == 2:
33 arr.append(int(x, base=16))
34 x = b""
35 if len(x) == 1:
36 x += b"0"
37 if x != b"":
38 arr.append(int(x, base=16))
39 return create_string_object(bytes(arr), forced_encoding)
42__ESCAPE_DICT__ = {
43 b"n": ord(b"\n"),
44 b"r": ord(b"\r"),
45 b"t": ord(b"\t"),
46 b"b": ord(b"\b"),
47 b"f": ord(b"\f"),
48 b"(": ord(b"("),
49 b")": ord(b")"),
50 b"/": ord(b"/"),
51 b"\\": ord(b"\\"),
52 b" ": ord(b" "),
53 b"%": ord(b"%"),
54 b"<": ord(b"<"),
55 b">": ord(b">"),
56 b"[": ord(b"["),
57 b"]": ord(b"]"),
58 b"#": ord(b"#"),
59 b"_": ord(b"_"),
60 b"&": ord(b"&"),
61 b"$": ord(b"$"),
62}
63__BACKSLASH_CODE__ = 92
66def read_string_from_stream(
67 stream: StreamType,
68 forced_encoding: Union[None, str, list[str], dict[int, str]] = None,
69) -> Union["TextStringObject", "ByteStringObject"]:
70 tok = stream.read(1)
71 parens = 1
72 txt = []
73 while True:
74 tok = stream.read(1)
75 if not tok:
76 raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
77 if tok == b"(":
78 parens += 1
79 elif tok == b")":
80 parens -= 1
81 if parens == 0:
82 break
83 elif tok == b"\\":
84 tok = stream.read(1)
85 try:
86 txt.append(__ESCAPE_DICT__[tok])
87 continue
88 except KeyError:
89 if b"0" <= tok <= b"7":
90 # "The number ddd may consist of one, two, or three
91 # octal digits; high-order overflow shall be ignored.
92 # Three octal digits shall be used, with leading zeros
93 # as needed, if the next character of the string is also
94 # a digit." (PDF reference 7.3.4.2, p 16)
95 sav = stream.tell() - 1
96 for _ in range(2):
97 ntok = stream.read(1)
98 if b"0" <= ntok <= b"7":
99 tok += ntok
100 else:
101 stream.seek(-1, 1) # ntok has to be analyzed
102 break
103 i = int(tok, base=8)
104 if i > 255:
105 txt.append(__BACKSLASH_CODE__)
106 stream.seek(sav)
107 else:
108 txt.append(i)
109 continue
110 if tok in b"\n\r":
111 # This case is hit when a backslash followed by a line
112 # break occurs. If it's a multi-char EOL, consume the
113 # second character:
114 tok = stream.read(1)
115 if tok not in b"\n\r":
116 stream.seek(-1, 1)
117 # Then don't add anything to the actual string, since this
118 # line break was escaped:
119 continue
120 logger_warning(
121 "Unexpected escaped string: %(token)s",
122 source=__name__,
123 token=tok.decode("utf-8", "ignore"),
124 )
125 txt.append(__BACKSLASH_CODE__)
126 txt.append(ord(tok))
127 return create_string_object(bytes(txt), forced_encoding)
130def create_string_object(
131 string: Union[str, bytes],
132 forced_encoding: Union[None, str, list[str], dict[int, str]] = None,
133) -> Union[TextStringObject, ByteStringObject]:
134 """
135 Create a ByteStringObject or a TextStringObject from a string to represent the string.
137 Args:
138 string: The data being used
139 forced_encoding: Typically None, or an encoding string
141 Returns:
142 A ByteStringObject
144 Raises:
145 TypeError: If string is not of type str or bytes.
147 """
148 if isinstance(string, str):
149 return TextStringObject(string)
150 if isinstance(string, bytes):
151 if isinstance(forced_encoding, (list, dict)):
152 out = ""
153 for x in string:
154 try:
155 out += forced_encoding[x]
156 except Exception:
157 out += bytes((x,)).decode("charmap")
158 obj = TextStringObject(out)
159 obj._original_bytes = string
160 return obj
161 if isinstance(forced_encoding, str):
162 if forced_encoding == "bytes":
163 return ByteStringObject(string)
164 obj = TextStringObject(string.decode(forced_encoding))
165 obj._original_bytes = string
166 return obj
167 try:
168 if string.startswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
169 retval = TextStringObject(string.decode("utf-16"))
170 retval._original_bytes = string
171 retval.autodetect_utf16 = True
172 retval.utf16_bom = string[:2]
173 return retval
174 if string.startswith(b"\x00"):
175 retval = TextStringObject(string.decode("utf-16be"))
176 retval._original_bytes = string
177 retval.autodetect_utf16 = True
178 retval.utf16_bom = codecs.BOM_UTF16_BE
179 return retval
180 if string[1:2] == b"\x00":
181 retval = TextStringObject(string.decode("utf-16le"))
182 retval._original_bytes = string
183 retval.autodetect_utf16 = True
184 retval.utf16_bom = codecs.BOM_UTF16_LE
185 return retval
187 # This is probably a big performance hit here, but we need
188 # to convert string objects into the text/unicode-aware
189 # version if possible... and the only way to check if that's
190 # possible is to try.
191 # Some strings are strings, some are just byte arrays.
192 retval = TextStringObject(decode_pdfdocencoding(string))
193 retval._original_bytes = string
194 retval.autodetect_pdfdocencoding = True
195 return retval
196 except UnicodeDecodeError:
197 return ByteStringObject(string)
198 else:
199 raise TypeError("create_string_object should have str or unicode arg")
202def decode_pdfdocencoding(byte_array: bytes) -> str:
203 retval = ""
204 for b in byte_array:
205 c = _pdfdoc_encoding[b]
206 if c == "\u0000":
207 raise UnicodeDecodeError(
208 "pdfdocencoding",
209 bytearray(b),
210 -1,
211 -1,
212 "does not exist in translation table",
213 )
214 retval += c
215 return retval