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

128 statements  

1import codecs 

2from typing import Union 

3 

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 

8 

9 

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] 

12 

13 

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) 

40 

41 

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 

64 

65 

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) 

128 

129 

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. 

136 

137 Args: 

138 string: The data being used 

139 forced_encoding: Typically None, or an encoding string 

140 

141 Returns: 

142 A ByteStringObject 

143 

144 Raises: 

145 TypeError: If string is not of type str or bytes. 

146 

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 

186 

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") 

200 

201 

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