Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/WmfImagePlugin.py: 62%

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

89 statements  

1# 

2# The Python Imaging Library 

3# $Id$ 

4# 

5# WMF stub codec 

6# 

7# history: 

8# 1996-12-14 fl Created 

9# 2004-02-22 fl Turned into a stub driver 

10# 2004-02-23 fl Added EMF support 

11# 

12# Copyright (c) Secret Labs AB 1997-2004. All rights reserved. 

13# Copyright (c) Fredrik Lundh 1996. 

14# 

15# See the README file for information on usage and redistribution. 

16# 

17# WMF/EMF reference documentation: 

18# https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf 

19# http://wvware.sourceforge.net/caolan/index.html 

20# http://wvware.sourceforge.net/caolan/ora-wmf.html 

21from __future__ import annotations 

22 

23__lazy_modules__ = {"PIL._binary"} 

24 

25from . import Image, ImageFile 

26from ._binary import i16le as word 

27from ._binary import si16le as short 

28from ._binary import si32le as _long 

29 

30TYPE_CHECKING = False 

31if TYPE_CHECKING: 

32 from typing import IO 

33 

34# 

35# -------------------------------------------------------------------- 

36# Read WMF file 

37 

38 

39def _accept(prefix: bytes) -> bool: 

40 return prefix.startswith((b"\xd7\xcd\xc6\x9a\x00\x00", b"\x01\x00\x00\x00")) 

41 

42 

43## 

44# Image plugin for Windows metafiles. 

45 

46 

47class WmfStubImageFile(ImageFile.StubImageFile): 

48 format = "WMF" 

49 format_description = "Windows Metafile" 

50 

51 def _open(self) -> None: 

52 # check placeable header 

53 assert self.fp is not None 

54 s = self.fp.read(44) 

55 

56 if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): 

57 # placeable Windows metafile 

58 

59 # get units per inch 

60 inch = word(s, 14) 

61 if inch == 0: 

62 msg = "Invalid inch" 

63 raise ValueError(msg) 

64 self._inch: tuple[float, float] = inch, inch 

65 

66 # get bounding box 

67 x0 = short(s, 6) 

68 y0 = short(s, 8) 

69 x1 = short(s, 10) 

70 y1 = short(s, 12) 

71 

72 # normalize size to 72 dots per inch 

73 self.info["dpi"] = 72 

74 size = ( 

75 (x1 - x0) * self.info["dpi"] // inch, 

76 (y1 - y0) * self.info["dpi"] // inch, 

77 ) 

78 

79 self.info["wmf_bbox"] = x0, y0, x1, y1 

80 

81 # sanity check (standard metafile header) 

82 if s[22:26] != b"\x01\x00\t\x00": 

83 msg = "Unsupported WMF file format" 

84 raise SyntaxError(msg) 

85 

86 elif s.startswith(b"\x01\x00\x00\x00") and s[40:44] == b" EMF": 

87 # enhanced metafile 

88 

89 # get bounding box 

90 x0 = _long(s, 8) 

91 y0 = _long(s, 12) 

92 x1 = _long(s, 16) 

93 y1 = _long(s, 20) 

94 

95 # get frame (in 0.01 millimeter units) 

96 frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36) 

97 

98 size = x1 - x0, y1 - y0 

99 

100 frame_width = frame[2] - frame[0] 

101 frame_height = frame[3] - frame[1] 

102 if frame_width == 0 or frame_height == 0: 

103 msg = "Invalid frame" 

104 raise ValueError(msg) 

105 

106 # calculate dots per inch from bbox and frame 

107 xdpi = 2540.0 * (x1 - x0) / frame_width 

108 ydpi = 2540.0 * (y1 - y0) / frame_height 

109 

110 self.info["wmf_bbox"] = x0, y0, x1, y1 

111 

112 if xdpi == ydpi: 

113 self.info["dpi"] = xdpi 

114 else: 

115 self.info["dpi"] = xdpi, ydpi 

116 self._inch = xdpi, ydpi 

117 

118 else: 

119 msg = "Unsupported file format" 

120 raise SyntaxError(msg) 

121 

122 self._mode = "RGB" 

123 self._size = size 

124 

125 def load( 

126 self, dpi: float | tuple[float, float] | None = None 

127 ) -> Image.core.PixelAccess | None: 

128 if dpi is not None: 

129 xy_dpi = dpi if isinstance(dpi, tuple) else (dpi, dpi) 

130 if any(v < 0 for v in xy_dpi): 

131 msg = "DPI must be non-negative" 

132 raise ValueError(msg) 

133 

134 self.info["dpi"] = dpi 

135 x0, y0, x1, y1 = self.info["wmf_bbox"] 

136 self._size = ( 

137 int((x1 - x0) * xy_dpi[0] / self._inch[0]), 

138 int((y1 - y0) * xy_dpi[1] / self._inch[1]), 

139 ) 

140 Image._decompression_bomb_check(self.size) 

141 return super().load() 

142 

143 

144def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

145 if WmfStubImageFile._handler is None or not hasattr( 

146 WmfStubImageFile._handler, "save" 

147 ): 

148 msg = "WMF save handler not installed" 

149 raise OSError(msg) 

150 WmfStubImageFile._handler.save(im, fp, filename) 

151 

152 

153def register_handler(handler: ImageFile.StubHandler | None) -> None: 

154 """ 

155 Install application-specific WMF image handler. 

156 

157 :param handler: Handler object. 

158 """ 

159 WmfStubImageFile._handler = handler 

160 

161 

162if hasattr(Image.core, "drawwmf"): 

163 # install default handler (Windows only) 

164 

165 class WmfHandler(ImageFile.StubHandler): 

166 def open(self, im: ImageFile.StubImageFile) -> None: 

167 self.bbox = im.info["wmf_bbox"] 

168 

169 def load(self, im: ImageFile.StubImageFile) -> Image.Image: 

170 if min(im.size) == 0: 

171 return Image.new("RGB", im.size) 

172 assert im.fp is not None 

173 im.fp.seek(0) # rewind 

174 return Image.frombytes( 

175 "RGB", 

176 im.size, 

177 Image.core.drawwmf(im.fp.read(), im.size, self.bbox), 

178 "raw", 

179 "BGR", 

180 (im.size[0] * 3 + 3) & -4, 

181 -1, 

182 ) 

183 

184 register_handler(WmfHandler()) 

185 

186 

187# 

188# -------------------------------------------------------------------- 

189# Registry stuff 

190 

191 

192Image.register_open(WmfStubImageFile.format, WmfStubImageFile, _accept) 

193Image.register_save(WmfStubImageFile.format, _save) 

194 

195Image.register_extensions(WmfStubImageFile.format, [".wmf", ".emf"])