1<?php 2 3namespace Mpdf\Image; 4 5use Mpdf\Color\ColorConverter; 6use Mpdf\Mpdf; 7 8class Wmf 9{ 10 11 /** 12 * @var \Mpdf\Mpdf 13 */ 14 private $mpdf; 15 16 /** 17 * @var \Mpdf\Color\ColorConverter 18 */ 19 private $colorConverter; 20 21 /** 22 * @var array 23 */ 24 private $gdiObjectArray; 25 26 public function __construct(Mpdf $mpdf, ColorConverter $colorConverter) 27 { 28 $this->mpdf = $mpdf; 29 $this->colorConverter = $colorConverter; 30 } 31 32 function _getWMFimage($data) 33 { 34 $k = Mpdf::SCALE; 35 36 $this->gdiObjectArray = []; 37 $a = unpack('stest', "\1\0"); 38 if ($a['test'] != 1) { 39 return [0, 'Error parsing WMF image - Big-endian architecture not supported']; 40 } 41 // check for Aldus placeable metafile header 42 $key = unpack('Lmagic', substr($data, 0, 4)); 43 $p = 18; // WMF header 44 if ($key['magic'] == (int) 0x9AC6CDD7) { 45 $p +=22; 46 } // Aldus header 47 // define some state variables 48 $wo = null; // window origin 49 $we = null; // window extent 50 $polyFillMode = 0; 51 $nullPen = false; 52 $nullBrush = false; 53 $endRecord = false; 54 $wmfdata = ''; 55 while ($p < strlen($data) && !$endRecord) { 56 $recordInfo = unpack('Lsize/Sfunc', substr($data, $p, 6)); 57 $p += 6; 58 // size of record given in WORDs (= 2 bytes) 59 $size = $recordInfo['size']; 60 // func is number of GDI function 61 $func = $recordInfo['func']; 62 if ($size > 3) { 63 $parms = substr($data, $p, 2 * ($size - 3)); 64 $p += 2 * ($size - 3); 65 } 66 switch ($func) { 67 case 0x020b: // SetWindowOrg 68 // do not allow window origin to be changed 69 // after drawing has begun 70 if (!$wmfdata) { 71 $wo = array_reverse(unpack('s2', $parms)); 72 } 73 break; 74 case 0x020c: // SetWindowExt 75 // do not allow window extent to be changed 76 // after drawing has begun 77 if (!$wmfdata) { 78 $we = array_reverse(unpack('s2', $parms)); 79 } 80 break; 81 case 0x02fc: // CreateBrushIndirect 82 $brush = unpack('sstyle/Cr/Cg/Cb/Ca/Shatch', $parms); 83 $brush['type'] = 'B'; 84 $this->_AddGDIObject($brush); 85 break; 86 case 0x02fa: // CreatePenIndirect 87 $pen = unpack('Sstyle/swidth/sdummy/Cr/Cg/Cb/Ca', $parms); 88 // convert width from twips to user unit 89 $pen['width'] /= (20 * $k); 90 $pen['type'] = 'P'; 91 $this->_AddGDIObject($pen); 92 break; 93 94 // MUST create other GDI objects even if we don't handle them 95 case 0x06fe: // CreateBitmap 96 case 0x02fd: // CreateBitmapIndirect 97 case 0x00f8: // CreateBrush 98 case 0x02fb: // CreateFontIndirect 99 case 0x00f7: // CreatePalette 100 case 0x01f9: // CreatePatternBrush 101 case 0x06ff: // CreateRegion 102 case 0x0142: // DibCreatePatternBrush 103 $dummyObject = ['type' => 'D']; 104 $this->_AddGDIObject($dummyObject); 105 break; 106 case 0x0106: // SetPolyFillMode 107 $polyFillMode = unpack('smode', $parms); 108 $polyFillMode = $polyFillMode['mode']; 109 break; 110 case 0x01f0: // DeleteObject 111 $idx = unpack('Sidx', $parms); 112 $idx = $idx['idx']; 113 $this->_DeleteGDIObject($idx); 114 break; 115 case 0x012d: // SelectObject 116 $idx = unpack('Sidx', $parms); 117 $idx = $idx['idx']; 118 $obj = $this->_GetGDIObject($idx); 119 switch ($obj['type']) { 120 case 'B': 121 $nullBrush = false; 122 if ($obj['style'] == 1) { 123 $nullBrush = true; 124 } else { 125 $wmfdata .= $this->mpdf->SetFColor($this->colorConverter->convert('rgb(' . $obj['r'] . ',' . $obj['g'] . ',' . $obj['b'] . ')', $this->mpdf->PDFAXwarnings), true) . "\n"; 126 } 127 break; 128 case 'P': 129 $nullPen = false; 130 $dashArray = []; 131 // dash parameters are custom 132 switch ($obj['style']) { 133 case 0: // PS_SOLID 134 break; 135 case 1: // PS_DASH 136 $dashArray = [3, 1]; 137 break; 138 case 2: // PS_DOT 139 $dashArray = [0.5, 0.5]; 140 break; 141 case 3: // PS_DASHDOT 142 $dashArray = [2, 1, 0.5, 1]; 143 break; 144 case 4: // PS_DASHDOTDOT 145 $dashArray = [2, 1, 0.5, 1, 0.5, 1]; 146 break; 147 case 5: // PS_NULL 148 $nullPen = true; 149 break; 150 } 151 if (!$nullPen) { 152 $wmfdata .= $this->mpdf->SetDColor($this->colorConverter->convert('rgb(' . $obj['r'] . ',' . $obj['g'] . ',' . $obj['b'] . ')', $this->mpdf->PDFAXwarnings), true) . "\n"; 153 $wmfdata .= sprintf("%.3F w\n", $obj['width'] * $k); 154 } 155 if (!empty($dashArray)) { 156 $s = '['; 157 for ($i = 0; $i < count($dashArray); $i++) { 158 $s .= $dashArray[$i] * $k; 159 if ($i != count($dashArray) - 1) { 160 $s .= ' '; 161 } 162 } 163 $s .= '] 0 d'; 164 $wmfdata .= $s . "\n"; 165 } 166 break; 167 } 168 break; 169 case 0x0325: // Polyline 170 case 0x0324: // Polygon 171 $coords = unpack('s' . ($size - 3), $parms); 172 $numpoints = $coords[1]; 173 for ($i = $numpoints; $i > 0; $i--) { 174 $px = $coords[2 * $i]; 175 $py = $coords[2 * $i + 1]; 176 177 if ($i < $numpoints) { 178 $wmfdata .= $this->_LineTo($px, $py); 179 } else { 180 $wmfdata .= $this->_MoveTo($px, $py); 181 } 182 } 183 if ($func == 0x0325) { 184 $op = 's'; 185 } elseif ($func == 0x0324) { 186 if ($nullPen) { 187 if ($nullBrush) { 188 $op = 'n'; 189 } // no op 190 else { 191 $op = 'f'; 192 } // fill 193 } else { 194 if ($nullBrush) { 195 $op = 's'; 196 } // stroke 197 else { 198 $op = 'b'; 199 } // stroke and fill 200 } 201 if ($polyFillMode == 1 && ($op == 'b' || $op == 'f')) { 202 $op .= '*'; 203 } // use even-odd fill rule 204 } 205 $wmfdata .= $op . "\n"; 206 break; 207 case 0x0538: // PolyPolygon 208 $coords = unpack('s' . ($size - 3), $parms); 209 $numpolygons = $coords[1]; 210 $adjustment = $numpolygons; 211 for ($j = 1; $j <= $numpolygons; $j++) { 212 $numpoints = $coords[$j + 1]; 213 for ($i = $numpoints; $i > 0; $i--) { 214 $px = $coords[2 * $i + $adjustment]; 215 $py = $coords[2 * $i + 1 + $adjustment]; 216 if ($i == $numpoints) { 217 $wmfdata .= $this->_MoveTo($px, $py); 218 } else { 219 $wmfdata .= $this->_LineTo($px, $py); 220 } 221 } 222 $adjustment += $numpoints * 2; 223 } 224 225 if ($nullPen) { 226 if ($nullBrush) { 227 $op = 'n'; 228 } // no op 229 else { 230 $op = 'f'; 231 } // fill 232 } else { 233 if ($nullBrush) { 234 $op = 's'; 235 } // stroke 236 else { 237 $op = 'b'; 238 } // stroke and fill 239 } 240 if ($polyFillMode == 1 && ($op == 'b' || $op == 'f')) { 241 $op .= '*'; 242 } // use even-odd fill rule 243 $wmfdata .= $op . "\n"; 244 break; 245 case 0x0000: 246 $endRecord = true; 247 break; 248 } 249 } 250 251 return [1, $wmfdata, $wo, $we]; 252 } 253 254 function _MoveTo($x, $y) 255 { 256 return "$x $y m\n"; 257 } 258 259 // a line must have been started using _MoveTo() first 260 function _LineTo($x, $y) 261 { 262 return "$x $y l\n"; 263 } 264 265 function _AddGDIObject($obj) 266 { 267 // find next available slot 268 $idx = 0; 269 if (!empty($this->gdiObjectArray)) { 270 $empty = false; 271 $i = 0; 272 while (!$empty) { 273 $empty = !isset($this->gdiObjectArray[$i]); 274 $i++; 275 } 276 $idx = $i - 1; 277 } 278 $this->gdiObjectArray[$idx] = $obj; 279 } 280 281 function _GetGDIObject($idx) 282 { 283 return $this->gdiObjectArray[$idx]; 284 } 285 286 function _DeleteGDIObject($idx) 287 { 288 unset($this->gdiObjectArray[$idx]); 289 } 290} 291