1<?php 2 3namespace Mpdf; 4 5use Mpdf\Color\ColorConverter; 6use Mpdf\Writer\BaseWriter; 7 8class Gradient 9{ 10 11 const TYPE_LINEAR = 2; 12 const TYPE_RADIAL = 3; 13 14 /** 15 * @var \Mpdf\Mpdf 16 */ 17 private $mpdf; 18 19 /** 20 * @var \Mpdf\SizeConverter 21 */ 22 private $sizeConverter; 23 24 /** 25 * @var \Mpdf\Color\ColorConverter 26 */ 27 private $colorConverter; 28 29 /** 30 * @var \Mpdf\Writer\BaseWriter 31 */ 32 private $writer; 33 34 public function __construct(Mpdf $mpdf, SizeConverter $sizeConverter, ColorConverter $colorConverter, BaseWriter $writer) 35 { 36 $this->mpdf = $mpdf; 37 $this->sizeConverter = $sizeConverter; 38 $this->colorConverter = $colorConverter; 39 $this->writer = $writer; 40 } 41 42 // mPDF 5.3.A1 43 public function CoonsPatchMesh($x, $y, $w, $h, $patch_array = [], $x_min = 0, $x_max = 1, $y_min = 0, $y_max = 1, $colspace = 'RGB', $return = false) 44 { 45 $s = ' q '; 46 $s.=sprintf(' %.3F %.3F %.3F %.3F re W n ', $x * Mpdf::SCALE, ($this->mpdf->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$h * Mpdf::SCALE); 47 $s.=sprintf(' %.3F 0 0 %.3F %.3F %.3F cm ', $w * Mpdf::SCALE, $h * Mpdf::SCALE, $x * Mpdf::SCALE, ($this->mpdf->h - ($y + $h)) * Mpdf::SCALE); 48 $n = count($this->mpdf->gradients) + 1; 49 $this->mpdf->gradients[$n]['type'] = 6; //coons patch mesh 50 $this->mpdf->gradients[$n]['colorspace'] = $colspace; //coons patch mesh 51 $bpcd = 65535; //16 BitsPerCoordinate 52 $trans = false; 53 $this->mpdf->gradients[$n]['stream'] = ''; 54 55 for ($i = 0; $i < count($patch_array); $i++) { 56 $this->mpdf->gradients[$n]['stream'].=chr($patch_array[$i]['f']); //start with the edge flag as 8 bit 57 58 for ($j = 0; $j < count($patch_array[$i]['points']); $j++) { 59 60 // each point as 16 bit 61 if (($j % 2) == 1) { // Y coordinate (adjusted as input is From top left) 62 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $y_min) / ($y_max - $y_min)) * $bpcd; 63 $patch_array[$i]['points'][$j] = $bpcd - $patch_array[$i]['points'][$j]; 64 } else { 65 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $x_min) / ($x_max - $x_min)) * $bpcd; 66 } 67 if ($patch_array[$i]['points'][$j] < 0) { 68 $patch_array[$i]['points'][$j] = 0; 69 } 70 if ($patch_array[$i]['points'][$j] > $bpcd) { 71 $patch_array[$i]['points'][$j] = $bpcd; 72 } 73 74 $this->mpdf->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256)); 75 $this->mpdf->gradients[$n]['stream'] .= chr(floor(round($patch_array[$i]['points'][$j]) % 256)); 76 } 77 78 for ($j = 0; $j < count($patch_array[$i]['colors']); $j++) { 79 //each color component as 8 bit 80 if ($colspace === 'RGB') { 81 $this->mpdf->gradients[$n]['stream'] .= $patch_array[$i]['colors'][$j][1]; 82 $this->mpdf->gradients[$n]['stream'] .= $patch_array[$i]['colors'][$j][2]; 83 $this->mpdf->gradients[$n]['stream'] .= $patch_array[$i]['colors'][$j][3]; 84 if (isset($patch_array[$i]['colors'][$j][4]) && ord($patch_array[$i]['colors'][$j][4]) < 100) { 85 $trans = true; 86 } 87 } elseif ($colspace === 'CMYK') { 88 $this->mpdf->gradients[$n]['stream'] .= chr(ord($patch_array[$i]['colors'][$j][1]) * 2.55); 89 $this->mpdf->gradients[$n]['stream'] .= chr(ord($patch_array[$i]['colors'][$j][2]) * 2.55); 90 $this->mpdf->gradients[$n]['stream'] .= chr(ord($patch_array[$i]['colors'][$j][3]) * 2.55); 91 $this->mpdf->gradients[$n]['stream'] .= chr(ord($patch_array[$i]['colors'][$j][4]) * 2.55); 92 if (isset($patch_array[$i]['colors'][$j][5]) && ord($patch_array[$i]['colors'][$j][5]) < 100) { 93 $trans = true; 94 } 95 } elseif ($colspace === 'Gray') { 96 $this->mpdf->gradients[$n]['stream'].= $patch_array[$i]['colors'][$j][1]; 97 if ($patch_array[$i]['colors'][$j][2] == 1) { 98 $trans = true; 99 } // transparency converted from rgba or cmyka() 100 } 101 } 102 } 103 104 // TRANSPARENCY 105 if ($trans) { 106 $this->mpdf->gradients[$n]['stream_trans'] = ''; 107 108 for ($i = 0; $i < count($patch_array); $i++) { 109 110 $this->mpdf->gradients[$n]['stream_trans'] .= chr($patch_array[$i]['f']); 111 112 for ($j = 0; $j < count($patch_array[$i]['points']); $j++) { 113 // each point as 16 bit 114 $this->mpdf->gradients[$n]['stream_trans'] .= chr(floor($patch_array[$i]['points'][$j] / 256)); 115 $this->mpdf->gradients[$n]['stream_trans'] .= chr(floor(round($patch_array[$i]['points'][$j]) % 256)); 116 } 117 118 for ($j = 0; $j < count($patch_array[$i]['colors']); $j++) { 119 // each color component as 8 bit // OPACITY 120 if ($colspace === 'RGB') { 121 $this->mpdf->gradients[$n]['stream_trans'] .= chr((int) (ord($patch_array[$i]['colors'][$j][4]) * 2.55)); 122 } elseif ($colspace === 'CMYK') { 123 $this->mpdf->gradients[$n]['stream_trans'] .= chr((int) (ord($patch_array[$i]['colors'][$j][5]) * 2.55)); 124 } elseif ($colspace === 'Gray') { 125 $this->mpdf->gradients[$n]['stream_trans'] .= chr((int) (ord($patch_array[$i]['colors'][$j][3]) * 2.55)); 126 } 127 } 128 } 129 130 $this->mpdf->gradients[$n]['trans'] = true; 131 $s .= ' /TGS' . $n . ' gs '; 132 } 133 134 // paint the gradient 135 $s .= '/Sh' . $n . ' sh' . "\n"; 136 137 // restore previous Graphic State 138 $s .= 'Q' . "\n"; 139 140 if ($return) { 141 return $s; 142 } 143 144 $this->writer->write($s); 145 } 146 147 // type = linear:2; radial: 3; 148 // Linear: $coords - array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). 149 // The default value is from left to right (x1=0, y1=0, x2=1, y2=0). 150 // Radial: $coords - array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, 151 // (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). 152 // (fx, fy) should be inside the circle, otherwise some areas will not be defined 153 // $col = array(R,G,B/255); or array(G/255); or array(C,M,Y,K/100) 154 // $stops = array('col'=>$col [, 'opacity'=>0-1] [, 'offset'=>0-1]) 155 public function Gradient($x, $y, $w, $h, $type, $stops = [], $colorspace = 'RGB', $coords = '', $extend = '', $return = false, $is_mask = false) 156 { 157 if ($type && stripos($type, 'L') === 0) { 158 $type = self::TYPE_LINEAR; 159 } elseif ($type && stripos($type, 'R') === 0) { 160 $type = self::TYPE_RADIAL; 161 } 162 163 if ($colorspace !== 'CMYK' && $colorspace !== 'Gray') { 164 $colorspace = 'RGB'; 165 } 166 167 $bboxw = $w; 168 $bboxh = $h; 169 $usex = $x; 170 $usey = $y; 171 $usew = $bboxw; 172 $useh = $bboxh; 173 174 if ($type < 1) { 175 $type = self::TYPE_LINEAR; 176 } 177 178 if ($coords[0] !== false && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $coords[0], $m)) { 179 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 180 if ($tmp) { 181 $coords[0] = $tmp / $w; 182 } 183 } 184 185 if ($coords[1] !== false && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $coords[1], $m)) { 186 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 187 if ($tmp) { 188 $coords[1] = 1 - ($tmp / $h); 189 } 190 } 191 192 if ($type == self::TYPE_LINEAR) { 193 $angle = (isset($coords[4]) ? $coords[4] : false); 194 $repeat = (isset($coords[5]) ? $coords[5] : false); 195 // ALL POINTS SET (default for custom mPDF linear gradient) - no -moz 196 if ($coords[0] !== false && $coords[1] !== false && $coords[2] !== false && $coords[3] !== false) { 197 // do nothing - coords used as they are 198 } elseif ($angle !== false && $coords[0] !== false && $coords[1] !== false && $coords[2] === false && $coords[3] === false) { 199 // If both a <point> and <angle> are defined, the gradient axis starts from the point and runs along the angle. The end point is 200 // defined as before - in this case start points may not be in corners, and axis may not correctly fall in the right quadrant. 201 // NO end points (Angle defined & Start points) 202 if ($angle == 0 || $angle == 360) { 203 $coords[3] = $coords[1]; 204 if ($coords[0] == 1) { 205 $coords[2] = 2; 206 } else { 207 $coords[2] = 1; 208 } 209 } elseif ($angle == 90) { 210 $coords[2] = $coords[0]; 211 $coords[3] = 1; 212 if ($coords[1] == 1) { 213 $coords[3] = 2; 214 } else { 215 $coords[3] = 1; 216 } 217 } elseif ($angle == 180) { 218 if ($coords[4] == 0) { 219 $coords[2] = -1; 220 } else { 221 $coords[2] = 0; 222 } 223 $coords[3] = $coords[1]; 224 } elseif ($angle == 270) { 225 $coords[2] = $coords[0]; 226 if ($coords[1] == 0) { 227 $coords[3] = -1; 228 } else { 229 $coords[3] = 0; 230 } 231 } else { 232 $endx = 1; 233 $endy = 1; 234 if ($angle <= 90) { 235 if ($angle <= 45) { 236 $endy = tan(deg2rad($angle)); 237 } else { 238 $endx = tan(deg2rad(90 - $angle)); 239 } 240 $b = atan2($endy * $bboxh, $endx * $bboxw); 241 $ny = 1 - $coords[1] - (tan($b) * (1 - $coords[0])); 242 $tx = sin($b) * cos($b) * $ny; 243 $ty = cos($b) * cos($b) * $ny; 244 $coords[2] = 1 + $tx; 245 $coords[3] = 1 - $ty; 246 } elseif ($angle <= 180) { 247 if ($angle <= 135) { 248 $endx = tan(deg2rad($angle - 90)); 249 } else { 250 $endy = tan(deg2rad(180 - $angle)); 251 } 252 $b = atan2($endy * $bboxh, $endx * $bboxw); 253 $ny = 1 - $coords[1] - (tan($b) * $coords[0]); 254 $tx = sin($b) * cos($b) * $ny; 255 $ty = cos($b) * cos($b) * $ny; 256 $coords[2] = -$tx; 257 $coords[3] = 1 - $ty; 258 } elseif ($angle <= 270) { 259 if ($angle <= 225) { 260 $endy = tan(deg2rad($angle - 180)); 261 } else { 262 $endx = tan(deg2rad(270 - $angle)); 263 } 264 $b = atan2($endy * $bboxh, $endx * $bboxw); 265 $ny = $coords[1] - (tan($b) * $coords[0]); 266 $tx = sin($b) * cos($b) * $ny; 267 $ty = cos($b) * cos($b) * $ny; 268 $coords[2] = -$tx; 269 $coords[3] = $ty; 270 } else { 271 if ($angle <= 315) { 272 $endx = tan(deg2rad($angle - 270)); 273 } else { 274 $endy = tan(deg2rad(360 - $angle)); 275 } 276 $b = atan2($endy * $bboxh, $endx * $bboxw); 277 $ny = $coords[1] - (tan($b) * (1 - $coords[0])); 278 $tx = sin($b) * cos($b) * $ny; 279 $ty = cos($b) * cos($b) * $ny; 280 $coords[2] = 1 + $tx; 281 $coords[3] = $ty; 282 } 283 } 284 } elseif ($angle !== false && $coords[0] === false && $coords[1] === false) { 285 // -moz If the first parameter is only an <angle>, the gradient axis starts from the box's corner that would ensure the 286 // axis goes through the box. The axis runs along the specified angle. The end point of the axis is defined such that the 287 // farthest corner of the box from the starting point is perpendicular to the gradient axis at that point. 288 // NO end points or Start points (Angle defined) 289 if ($angle == 0 || $angle == 360) { 290 $coords[0] = 0; 291 $coords[1] = 0; 292 $coords[2] = 1; 293 $coords[3] = 0; 294 } elseif ($angle == 90) { 295 $coords[0] = 0; 296 $coords[1] = 0; 297 $coords[2] = 0; 298 $coords[3] = 1; 299 } elseif ($angle == 180) { 300 $coords[0] = 1; 301 $coords[1] = 0; 302 $coords[2] = 0; 303 $coords[3] = 0; 304 } elseif ($angle == 270) { 305 $coords[0] = 0; 306 $coords[1] = 1; 307 $coords[2] = 0; 308 $coords[3] = 0; 309 } else { 310 if ($angle <= 90) { 311 $coords[0] = 0; 312 $coords[1] = 0; 313 if ($angle <= 45) { 314 $endx = 1; 315 $endy = tan(deg2rad($angle)); 316 } else { 317 $endx = tan(deg2rad(90 - $angle)); 318 $endy = 1; 319 } 320 } elseif ($angle <= 180) { 321 $coords[0] = 1; 322 $coords[1] = 0; 323 if ($angle <= 135) { 324 $endx = tan(deg2rad($angle - 90)); 325 $endy = 1; 326 } else { 327 $endx = 1; 328 $endy = tan(deg2rad(180 - $angle)); 329 } 330 } elseif ($angle <= 270) { 331 $coords[0] = 1; 332 $coords[1] = 1; 333 if ($angle <= 225) { 334 $endx = 1; 335 $endy = tan(deg2rad($angle - 180)); 336 } else { 337 $endx = tan(deg2rad(270 - $angle)); 338 $endy = 1; 339 } 340 } else { 341 $coords[0] = 0; 342 $coords[1] = 1; 343 if ($angle <= 315) { 344 $endx = tan(deg2rad($angle - 270)); 345 $endy = 1; 346 } else { 347 $endx = 1; 348 $endy = tan(deg2rad(360 - $angle)); 349 } 350 } 351 $b = atan2($endy * $bboxh, $endx * $bboxw); 352 $h2 = $bboxh - ($bboxh * tan($b)); 353 $px = $bboxh + ($h2 * sin($b) * cos($b)); 354 $py = ($bboxh * tan($b)) + ($h2 * sin($b) * sin($b)); 355 $x1 = $px / $bboxh; 356 $y1 = $py / $bboxh; 357 if ($angle <= 90) { 358 $coords[2] = $x1; 359 $coords[3] = $y1; 360 } elseif ($angle <= 180) { 361 $coords[2] = 1 - $x1; 362 $coords[3] = $y1; 363 } elseif ($angle <= 270) { 364 $coords[2] = 1 - $x1; 365 $coords[3] = 1 - $y1; 366 } else { 367 $coords[2] = $x1; 368 $coords[3] = 1 - $y1; 369 } 370 } 371 } elseif ((!isset($angle) || $angle === false) && $coords[0] !== false && $coords[1] !== false) { 372 // -moz If the first parameter to the gradient function is only a <point>, the gradient axis starts from the specified point, 373 // and ends at the point you would get if you rotated the starting point by 180 degrees about the center of the box that the 374 // gradient is to be applied to. 375 // NO angle and NO end points (Start points defined) 376 $coords[2] = 1 - $coords[0]; 377 $coords[3] = 1 - $coords[1]; 378 $angle = rad2deg(atan2($coords[3] - $coords[1], $coords[2] - $coords[0])); 379 if ($angle < 0) { 380 $angle += 360; 381 } elseif ($angle > 360) { 382 $angle -= 360; 383 } 384 if ($angle != 0 && $angle != 360 && $angle != 90 && $angle != 180 && $angle != 270) { 385 if ($w >= $h) { 386 $coords[1] *= $h / $w; 387 $coords[3] *= $h / $w; 388 $usew = $useh = $bboxw; 389 $usey -= ($w - $h); 390 } else { 391 $coords[0] *= $w / $h; 392 $coords[2] *= $w / $h; 393 $usew = $useh = $bboxh; 394 } 395 } 396 } else { 397 // default values T2B 398 // -moz If neither a <point> or <angle> is specified, i.e. the entire function consists of only <stop> values, the gradient 399 // axis starts from the top of the box and runs vertically downwards, ending at the bottom of the box. 400 // All values are set in parseMozGradient - so won't appear here 401 $coords = [0, 0, 1, 0]; // default for original linear gradient (L2R) 402 } 403 } elseif ($type == self::TYPE_RADIAL) { 404 $radius = (isset($coords[4]) ? $coords[4] : false); 405 $shape = (isset($coords[6]) ? $coords[6] : false); 406 $size = (isset($coords[7]) ? $coords[7] : false); 407 $repeat = (isset($coords[8]) ? $coords[8] : false); 408 // ALL POINTS AND RADIUS SET (default for custom mPDF radial gradient) - no -moz 409 if ($coords[0] !== false && $coords[1] !== false && $coords[2] !== false && $coords[3] !== false && $coords[4] !== false) { 410 // If a <point> is defined 411 // do nothing - coords used as they are 412 } elseif ($shape !== false && $size !== false) { 413 if ($coords[2] == false) { 414 $coords[2] = $coords[0]; 415 } 416 if ($coords[3] == false) { 417 $coords[3] = $coords[1]; 418 } 419 // ELLIPSE 420 if ($shape === 'ellipse') { 421 $corner1 = sqrt(($coords[0] ** 2) + ($coords[1] ** 2)); 422 $corner2 = sqrt(($coords[0] ** 2) + ((1 - $coords[1]) ** 2)); 423 $corner3 = sqrt(((1 - $coords[0]) ** 2) + ($coords[1] ** 2)); 424 $corner4 = sqrt(((1 - $coords[0]) ** 2) + ((1 - $coords[1]) ** 2)); 425 if ($size === 'closest-side') { 426 $radius = min($coords[0], $coords[1], 1 - $coords[0], 1 - $coords[1]); 427 } elseif ($size === 'closest-corner') { 428 $radius = min($corner1, $corner2, $corner3, $corner4); 429 } elseif ($size === 'farthest-side') { 430 $radius = max($coords[0], $coords[1], 1 - $coords[0], 1 - $coords[1]); 431 } else { 432 $radius = max($corner1, $corner2, $corner3, $corner4); 433 } // farthest corner (default) 434 } elseif ($shape === 'circle') { 435 if ($w >= $h) { 436 $coords[1] = $coords[3] = ($coords[1] * $h / $w); 437 $corner1 = sqrt(($coords[0] ** 2) + ($coords[1] ** 2)); 438 $corner2 = sqrt(($coords[0] ** 2) + ((($h / $w) - $coords[1]) ** 2)); 439 $corner3 = sqrt(((1 - $coords[0]) ** 2) + ($coords[1] ** 2)); 440 $corner4 = sqrt(((1 - $coords[0]) ** 2) + ((($h / $w) - $coords[1]) ** 2)); 441 if ($size === 'closest-side') { 442 $radius = min($coords[0], $coords[1], 1 - $coords[0], ($h / $w) - $coords[1]); 443 } elseif ($size === 'closest-corner') { 444 $radius = min($corner1, $corner2, $corner3, $corner4); 445 } elseif ($size === 'farthest-side') { 446 $radius = max($coords[0], $coords[1], 1 - $coords[0], ($h / $w) - $coords[1]); 447 } elseif ($size === 'farthest-corner') { 448 $radius = max($corner1, $corner2, $corner3, $corner4); 449 } // farthest corner (default) 450 $usew = $useh = $bboxw; 451 $usey -= ($w - $h); 452 } else { 453 $coords[0] = $coords[2] = ($coords[0] * $w / $h); 454 $corner1 = sqrt(($coords[0] ** 2) + ($coords[1] ** 2)); 455 $corner2 = sqrt(($coords[0] ** 2) + ((1 - $coords[1]) ** 2)); 456 $corner3 = sqrt(((($w / $h) - $coords[0]) ** 2) + ($coords[1] ** 2)); 457 $corner4 = sqrt(((($w / $h) - $coords[0]) ** 2) + ((1 - $coords[1]) ** 2)); 458 if ($size === 'closest-side') { 459 $radius = min($coords[0], $coords[1], ($w / $h) - $coords[0], 1 - $coords[1]); 460 } elseif ($size === 'closest-corner') { 461 $radius = min($corner1, $corner2, $corner3, $corner4); 462 } elseif ($size === 'farthest-side') { 463 $radius = max($coords[0], $coords[1], ($w / $h) - $coords[0], 1 - $coords[1]); 464 } elseif ($size === 'farthest-corner') { 465 $radius = max($corner1, $corner2, $corner3, $corner4); 466 } // farthest corner (default) 467 $usew = $useh = $bboxh; 468 } 469 } 470 if ($radius == 0) { 471 $radius = 0.001; 472 } // to prevent error 473 $coords[4] = $radius; 474 } else { 475 // -moz If entire function consists of only <stop> values 476 // All values are set in parseMozGradient - so won't appear here 477 $coords = [0.5, 0.5, 0.5, 0.5]; // default for radial gradient (centred) 478 } 479 } 480 $s = ' q'; 481 $s .= sprintf(' %.3F %.3F %.3F %.3F re W n', $x * Mpdf::SCALE, ($this->mpdf->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$h * Mpdf::SCALE) . "\n"; 482 $s .= sprintf(' %.3F 0 0 %.3F %.3F %.3F cm', $usew * Mpdf::SCALE, $useh * Mpdf::SCALE, $usex * Mpdf::SCALE, ($this->mpdf->h - ($usey + $useh)) * Mpdf::SCALE) . "\n"; 483 484 $n = count($this->mpdf->gradients) + 1; 485 $this->mpdf->gradients[$n]['type'] = $type; 486 $this->mpdf->gradients[$n]['colorspace'] = $colorspace; 487 $trans = false; 488 $this->mpdf->gradients[$n]['is_mask'] = $is_mask; 489 if ($is_mask) { 490 $trans = true; 491 } 492 if (count($stops) == 1) { 493 $stops[1] = $stops[0]; 494 } 495 if (!isset($stops[0]['offset'])) { 496 $stops[0]['offset'] = 0; 497 } 498 if (!isset($stops[count($stops) - 1]['offset'])) { 499 $stops[count($stops) - 1]['offset'] = 1; 500 } 501 502 // Fix stop-offsets set as absolute lengths 503 if ($type == self::TYPE_LINEAR) { 504 $axisx = ($coords[2] - $coords[0]) * $usew; 505 $axisy = ($coords[3] - $coords[1]) * $useh; 506 $axis_length = sqrt(($axisx ** 2) + ($axisy ** 2)); 507 } else { 508 $axis_length = $coords[4] * $usew; 509 } // Absolute lengths are meaningless for an ellipse - Firefox uses Width as reference 510 511 for ($i = 0; $i < count($stops); $i++) { 512 if (isset($stops[$i]['offset']) && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $stops[$i]['offset'], $m)) { 513 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 514 $stops[$i]['offset'] = $axis_length ? $tmp / $axis_length : 0; 515 } 516 } 517 518 519 if (isset($stops[0]['offset']) && $stops[0]['offset'] > 0) { 520 $firststop = $stops[0]; 521 $firststop['offset'] = 0; 522 array_unshift($stops, $firststop); 523 } 524 if (!$repeat && isset($stops[count($stops) - 1]['offset']) && $stops[count($stops) - 1]['offset'] < 1) { 525 $endstop = $stops[count($stops) - 1]; 526 $endstop['offset'] = 1; 527 $stops[] = $endstop; 528 } 529 if ($stops[0]['offset'] > $stops[count($stops) - 1]['offset']) { 530 $stops[0]['offset'] = 0; 531 $stops[count($stops) - 1]['offset'] = 1; 532 } 533 534 for ($i = 0; $i < count($stops); $i++) { 535 // mPDF 5.3.74 536 if ($colorspace === 'CMYK') { 537 $this->mpdf->gradients[$n]['stops'][$i]['col'] = sprintf('%.3F %.3F %.3F %.3F', ord($stops[$i]['col'][1]) / 100, ord($stops[$i]['col'][2]) / 100, ord($stops[$i]['col'][3]) / 100, ord($stops[$i]['col'][4]) / 100); 538 } elseif ($colorspace === 'Gray') { 539 $this->mpdf->gradients[$n]['stops'][$i]['col'] = sprintf('%.3F', ord($stops[$i]['col'][1]) / 255); 540 } else { 541 $this->mpdf->gradients[$n]['stops'][$i]['col'] = sprintf('%.3F %.3F %.3F', ord($stops[$i]['col'][1]) / 255, ord($stops[$i]['col'][2]) / 255, ord($stops[$i]['col'][3]) / 255); 542 } 543 if (!isset($stops[$i]['opacity'])) { 544 $stops[$i]['opacity'] = 1; 545 } elseif ($stops[$i]['opacity'] > 1 || $stops[$i]['opacity'] < 0) { 546 $stops[$i]['opacity'] = 1; 547 } elseif ($stops[$i]['opacity'] < 1) { 548 $trans = true; 549 } 550 $this->mpdf->gradients[$n]['stops'][$i]['opacity'] = $stops[$i]['opacity']; 551 // OFFSET 552 if ($i > 0 && $i < (count($stops) - 1)) { 553 if (!isset($stops[$i]['offset']) || (isset($stops[$i + 1]['offset']) && $stops[$i]['offset'] > $stops[$i + 1]['offset']) || $stops[$i]['offset'] < $stops[$i - 1]['offset']) { 554 if (isset($stops[$i - 1]['offset']) && isset($stops[$i + 1]['offset'])) { 555 $stops[$i]['offset'] = ($stops[$i - 1]['offset'] + $stops[$i + 1]['offset']) / 2; 556 } else { 557 for ($j = ($i + 1); $j < count($stops); $j++) { 558 if (isset($stops[$j]['offset'])) { 559 break; 560 } 561 } 562 $int = ($stops[$j]['offset'] - $stops[$i - 1]['offset']) / ($j - $i + 1); 563 for ($f = 0; $f < ($j - $i - 1); $f++) { 564 $stops[$i + $f]['offset'] = $stops[$i + $f - 1]['offset'] + $int; 565 } 566 } 567 } 568 } 569 $this->mpdf->gradients[$n]['stops'][$i]['offset'] = $stops[$i]['offset']; 570 } 571 572 if ($repeat) { 573 $ns = count($this->mpdf->gradients[$n]['stops']); 574 $offs = []; 575 for ($i = 0; $i < $ns; $i++) { 576 $offs[$i] = $this->mpdf->gradients[$n]['stops'][$i]['offset']; 577 } 578 $gp = 0; 579 $inside = true; 580 while ($inside) { 581 $gp++; 582 for ($i = 0; $i < $ns; $i++) { 583 $this->mpdf->gradients[$n]['stops'][($ns * $gp) + $i] = $this->mpdf->gradients[$n]['stops'][($ns * ($gp - 1)) + $i]; 584 $tmp = $this->mpdf->gradients[$n]['stops'][($ns * ($gp - 1)) + ($ns - 1)]['offset'] + $offs[$i]; 585 if ($tmp < 1) { 586 $this->mpdf->gradients[$n]['stops'][($ns * $gp) + $i]['offset'] = $tmp; 587 } else { 588 $this->mpdf->gradients[$n]['stops'][($ns * $gp) + $i]['offset'] = 1; 589 $inside = false; 590 break; 591 } 592 } 593 } 594 } 595 596 if ($trans) { 597 $this->mpdf->gradients[$n]['trans'] = true; 598 $s .= ' /TGS' . $n . ' gs '; 599 } 600 if (!is_array($extend) || count($extend) < 1) { 601 $extend = ['true', 'true']; // These are supposed to be quoted - appear in PDF file as text 602 } 603 $this->mpdf->gradients[$n]['coords'] = $coords; 604 $this->mpdf->gradients[$n]['extend'] = $extend; 605 //paint the gradient 606 $s .= '/Sh' . $n . ' sh ' . "\n"; 607 //restore previous Graphic State 608 $s .= ' Q ' . "\n"; 609 if ($return) { 610 return $s; 611 } 612 613 $this->writer->write($s); 614 } 615 616 private function parseMozLinearGradient($m, $repeat) 617 { 618 $g = []; 619 $g['type'] = self::TYPE_LINEAR; 620 $g['colorspace'] = 'RGB'; 621 $g['extend'] = ['true', 'true']; 622 $v = trim($m[1]); 623 // Change commas inside e.g. rgb(x,x,x) 624 while (preg_match('/(\([^\)]*?),/', $v)) { 625 $v = preg_replace('/(\([^\)]*?),/', '\\1@', $v); 626 } 627 // Remove spaces inside e.g. rgb(x, x, x) 628 while (preg_match('/(\([^\)]*?)[ ]/', $v)) { 629 $v = preg_replace('/(\([^\)]*?)[ ]/', '\\1', $v); 630 } 631 $bgr = preg_split('/\s*,\s*/', $v); 632 for ($i = 0; $i < count($bgr); $i++) { 633 $bgr[$i] = preg_replace('/@/', ',', $bgr[$i]); 634 } 635 // Is first part $bgr[0] a valid point/angle? 636 $first = preg_split('/\s+/', trim($bgr[0])); 637 if (preg_match('/(left|center|right|bottom|top|deg|grad|rad)/i', $bgr[0]) && !preg_match('/(<#|rgb|rgba|hsl|hsla)/i', $bgr[0])) { 638 $startStops = 1; 639 } elseif (trim($first[count($first) - 1]) === '0') { 640 $startStops = 1; 641 } else { 642 $check = $this->colorConverter->convert($first[0], $this->mpdf->PDFAXwarnings); 643 $startStops = 1; 644 if ($check) { 645 $startStops = 0; 646 } 647 } 648 // first part a valid point/angle? 649 if ($startStops === 1) { // default values 650 // [<point> || <angle>,] = [<% em px left center right bottom top> || <deg grad rad 0>,] 651 if (preg_match('/([\-]*[0-9\.]+)(deg|grad|rad)/i', $bgr[0], $m)) { 652 $angle = $m[1] + 0; 653 if (strtolower($m[2]) === 'grad') { 654 $angle *= (360 / 400); 655 } elseif (strtolower($m[2]) === 'rad') { 656 $angle = rad2deg($angle); 657 } 658 while ($angle < 0) { 659 $angle += 360; 660 } 661 $angle %= 360; 662 } elseif (trim($first[count($first) - 1]) === '0') { 663 $angle = 0; 664 } 665 if (stripos($bgr[0], 'left') !== false) { 666 $startx = 0; 667 } elseif (stripos($bgr[0], 'right') !== false) { 668 $startx = 1; 669 } 670 if (stripos($bgr[0], 'top') !== false) { 671 $starty = 1; 672 } elseif (stripos($bgr[0], 'bottom') !== false) { 673 $starty = 0; 674 } 675 // Check for %? ?% or %% 676 if (preg_match('/(\d+)[%]/i', $first[0], $m)) { 677 $startx = $m[1] / 100; 678 } elseif (!isset($startx) && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $first[0], $m)) { 679 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 680 if ($tmp) { 681 $startx = $m[1]; 682 } 683 } 684 if (isset($first[1]) && preg_match('/(\d+)[%]/i', $first[1], $m)) { 685 $starty = 1 - ($m[1] / 100); 686 } elseif (!isset($starty) && isset($first[1]) && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $first[1], $m)) { 687 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 688 if ($tmp) { 689 $starty = $m[1]; 690 } 691 } 692 if (isset($startx) && !isset($starty)) { 693 $starty = 0.5; 694 } 695 if (!isset($startx) && isset($starty)) { 696 $startx = 0.5; 697 } 698 } else { 699 // If neither a <point> or <angle> is specified, i.e. the entire function consists of only <stop> values, 700 // the gradient axis starts from the top of the box and runs vertically downwards, ending at the bottom of 701 // the box. 702 $starty = 1; 703 $startx = 0.5; 704 $endy = 0; 705 $endx = 0.5; 706 } 707 if (!isset($startx)) { 708 $startx = false; 709 } 710 if (!isset($starty)) { 711 $starty = false; 712 } 713 if (!isset($endx)) { 714 $endx = false; 715 } 716 if (!isset($endy)) { 717 $endy = false; 718 } 719 if (!isset($angle)) { 720 $angle = false; 721 } 722 $g['coords'] = [$startx, $starty, $endx, $endy, $angle, $repeat]; 723 $g['stops'] = []; 724 for ($i = $startStops; $i < count($bgr); $i++) { 725 // parse stops 726 $el = preg_split('/\s+/', trim($bgr[$i])); 727 // mPDF 5.3.74 728 $col = $this->colorConverter->convert($el[0], $this->mpdf->PDFAXwarnings); 729 if (!$col) { 730 $col = $this->colorConverter->convert(255, $this->mpdf->PDFAXwarnings); 731 } 732 if ($col[0] == 1) { 733 $g['colorspace'] = 'Gray'; 734 } elseif ($col[0] == 4 || $col[0] == 6) { 735 $g['colorspace'] = 'CMYK'; 736 } 737 738 $g['stops'][] = $this->getStop($col, $el, true); 739 } 740 return $g; 741 } 742 743 private function parseMozRadialGradient($m, $repeat) 744 { 745 $g = []; 746 $g['type'] = self::TYPE_RADIAL; 747 $g['colorspace'] = 'RGB'; 748 $g['extend'] = ['true', 'true']; 749 $v = trim($m[1]); 750 // Change commas inside e.g. rgb(x,x,x) 751 while (preg_match('/(\([^\)]*?),/', $v)) { 752 $v = preg_replace('/(\([^\)]*?),/', '\\1@', $v); 753 } 754 // Remove spaces inside e.g. rgb(x, x, x) 755 while (preg_match('/(\([^\)]*?)[ ]/', $v)) { 756 $v = preg_replace('/(\([^\)]*?)[ ]/', '\\1', $v); 757 } 758 $bgr = preg_split('/\s*,\s*/', $v); 759 for ($i = 0; $i < count($bgr); $i++) { 760 $bgr[$i] = preg_replace('/@/', ',', $bgr[$i]); 761 } 762 763 // Is first part $bgr[0] a valid point/angle? 764 $startStops = 0; 765 $pos_angle = false; 766 $shape_size = false; 767 $first = preg_split('/\s+/', trim($bgr[0])); 768 $checkCol = $this->colorConverter->convert($first[0], $this->mpdf->PDFAXwarnings); 769 if (preg_match('/(left|center|right|bottom|top|deg|grad|rad)/i', $bgr[0]) && !preg_match('/(<#|rgb|rgba|hsl|hsla)/i', $bgr[0])) { 770 $startStops = 1; 771 $pos_angle = $bgr[0]; 772 } elseif (trim($first[count($first) - 1]) === '0') { 773 $startStops = 1; 774 $pos_angle = $bgr[0]; 775 } elseif (preg_match('/(circle|ellipse|closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)/i', $bgr[0])) { 776 $startStops = 1; 777 $shape_size = $bgr[0]; 778 } elseif (!$checkCol) { 779 $startStops = 1; 780 $pos_angle = $bgr[0]; 781 } 782 if (preg_match('/(circle|ellipse|closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)/i', $bgr[1])) { 783 $startStops = 2; 784 $shape_size = $bgr[1]; 785 } 786 787 // If valid point/angle? 788 if ($pos_angle) { // default values 789 // [<point> || <angle>,] = [<% em px left center right bottom top> || <deg grad rad 0>,] 790 if (stripos($pos_angle, 'left') !== false) { 791 $startx = 0; 792 } elseif (stripos($pos_angle, 'right') !== false) { 793 $startx = 1; 794 } 795 if (stripos($pos_angle, 'top') !== false) { 796 $starty = 1; 797 } elseif (stripos($pos_angle, 'bottom') !== false) { 798 $starty = 0; 799 } 800 // Check for %? ?% or %% 801 if (preg_match('/(\d+)[%]/i', $first[0], $m)) { 802 $startx = $m[1] / 100; 803 } elseif (!isset($startx) && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $first[0], $m)) { 804 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 805 if ($tmp) { 806 $startx = $m[1]; 807 } 808 } 809 if (isset($first[1]) && preg_match('/(\d+)[%]/i', $first[1], $m)) { 810 $starty = 1 - ($m[1] / 100); 811 } elseif (!isset($starty) && isset($first[1]) && preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $first[1], $m)) { 812 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 813 if ($tmp) { 814 $starty = $m[1]; 815 } 816 } 817 818 if (!isset($starty)) { 819 $starty = 0.5; 820 } 821 if (!isset($startx)) { 822 $startx = 0.5; 823 } 824 } else { 825 // If neither a <point> or <angle> is specified, i.e. the entire function consists of only <stop> values, 826 // the gradient axis starts from the top of the box and runs vertically downwards, ending at the bottom of 827 // the box. default values Center 828 $starty = 0.5; 829 $startx = 0.5; 830 $endy = 0.5; 831 $endx = 0.5; 832 } 833 834 // If valid shape/size? 835 $shape = 'ellipse'; // default 836 $size = 'farthest-corner'; // default 837 if ($shape_size) { // default values 838 if (preg_match('/(circle|ellipse)/i', $shape_size, $m)) { 839 $shape = $m[1]; 840 } 841 if (preg_match('/(closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)/i', $shape_size, $m)) { 842 $size = $m[1]; 843 if ($size === 'contain') { 844 $size = 'closest-side'; 845 } elseif ($size === 'cover') { 846 $size = 'farthest-corner'; 847 } 848 } 849 } 850 851 if (!isset($startx)) { 852 $startx = false; 853 } 854 if (!isset($starty)) { 855 $starty = false; 856 } 857 if (!isset($endx)) { 858 $endx = false; 859 } 860 if (!isset($endy)) { 861 $endy = false; 862 } 863 $radius = false; 864 $angle = 0; 865 $g['coords'] = [$startx, $starty, $endx, $endy, $radius, $angle, $shape, $size, $repeat]; 866 867 $g['stops'] = []; 868 for ($i = $startStops; $i < count($bgr); $i++) { 869 // parse stops 870 $el = preg_split('/\s+/', trim($bgr[$i])); 871 // mPDF 5.3.74 872 $col = $this->colorConverter->convert($el[0], $this->mpdf->PDFAXwarnings); 873 if (!$col) { 874 $col = $this->colorConverter->convert(255, $this->mpdf->PDFAXwarnings); 875 } 876 if ($col[0] == 1) { 877 $g['colorspace'] = 'Gray'; 878 } elseif ($col[0] == 4 || $col[0] == 6) { 879 $g['colorspace'] = 'CMYK'; 880 } 881 $g['stops'][] = $this->getStop($col, $el); 882 } 883 return $g; 884 } 885 886 private function getStop($col, $el, $convertOffset = false) 887 { 888 $stop = [ 889 'col' => $col, 890 ]; 891 892 if ($col[0] == 5) { 893 // transparency from rgba() 894 $stop['opacity'] = ord($col[4]) / 100; 895 } elseif ($col[0] == 6) { 896 // transparency from cmyka() 897 $stop['opacity'] = ord($col[5]) / 100; 898 } elseif ($col[0] == 1 && $col[2] == 1) { 899 // transparency converted from rgba or cmyka() 900 $stop['opacity'] = ord($col[3]) / 100; 901 } 902 903 if (isset($el[1])) { 904 if (preg_match('/(\d+)[%]/', $el[1], $m)) { 905 $stop['offset'] = $m[1] / 100; 906 if ($stop['offset'] > 1) { 907 unset($stop['offset']); 908 } 909 } elseif (preg_match('/([0-9.]+(px|em|ex|pc|pt|cm|mm|in))/i', $el[1], $m)) { 910 if ($convertOffset) { 911 $tmp = $this->sizeConverter->convert($m[1], $this->mpdf->w, $this->mpdf->FontSize, false); 912 if ($tmp) { 913 $stop['offset'] = $m[1]; 914 } 915 } else { 916 $stop['offset'] = $el[1]; 917 } 918 } 919 } 920 921 return $stop; 922 } 923 924 public function parseMozGradient($bg) 925 { 926 // background[-image]: -moz-linear-gradient(left, #c7Fdde 20%, #FF0000 ); 927 // background[-image]: linear-gradient(left, #c7Fdde 20%, #FF0000 ); // CSS3 928 $repeat = strpos($bg, 'repeating-') !== false; 929 930 if (preg_match('/linear-gradient\((.*)\)/', $bg, $m)) { 931 $g = $this->parseMozLinearGradient($m, $repeat); 932 if (count($g['stops'])) { 933 return $g; 934 } 935 } elseif (preg_match('/radial-gradient\((.*)\)/', $bg, $m)) { 936 $g = $this->parseMozRadialGradient($m, $repeat); 937 if (count($g['stops'])) { 938 return $g; 939 } 940 } 941 return []; 942 } 943 944 public function parseBackgroundGradient($bg) 945 { 946 // background-gradient: linear #00FFFF #FFFF00 0 0.5 1 0.5; or 947 // background-gradient: radial #00FFFF #FFFF00 0.5 0.5 1 1 1.2; 948 949 $v = trim($bg); 950 $bgr = preg_split('/\s+/', $v); 951 $count_bgr = count($bgr); 952 $g = []; 953 if ($count_bgr > 6) { 954 if (stripos($bgr[0], 'L') === 0 && $count_bgr === 7) { // linear 955 $g['type'] = self::TYPE_LINEAR; 956 //$coords = array(0,0,1,1 ); // 0 0 1 0 or 0 1 1 1 is L 2 R; 1,1,0,1 is R2L; 1,1,1,0 is T2B; 1,0,1,1 is B2T 957 // Linear: $coords - array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). 958 // The default value is from left to right (x1=0, y1=0, x2=1, y2=0). 959 $g['coords'] = [$bgr[3], $bgr[4], $bgr[5], $bgr[6]]; 960 } elseif ($count_bgr === 8) { // radial 961 $g['type'] = self::TYPE_RADIAL; 962 // Radial: $coords - array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, 963 // (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). 964 // (fx, fy) should be inside the circle, otherwise some areas will not be defined 965 $g['coords'] = [$bgr[3], $bgr[4], $bgr[5], $bgr[6], $bgr[7]]; 966 } 967 $g['colorspace'] = 'RGB'; 968 // mPDF 5.3.74 969 $cor = $this->colorConverter->convert($bgr[1], $this->mpdf->PDFAXwarnings); 970 if ($cor[0] == 1) { 971 $g['colorspace'] = 'Gray'; 972 } elseif ($cor[0] == 4 || $cor[0] == 6) { 973 $g['colorspace'] = 'CMYK'; 974 } 975 if ($cor) { 976 $g['col'] = $cor; 977 } else { 978 $g['col'] = $this->colorConverter->convert(255, $this->mpdf->PDFAXwarnings); 979 } 980 $cor = $this->colorConverter->convert($bgr[2], $this->mpdf->PDFAXwarnings); 981 if ($cor) { 982 $g['col2'] = $cor; 983 } else { 984 $g['col2'] = $this->colorConverter->convert(255, $this->mpdf->PDFAXwarnings); 985 } 986 $g['extend'] = ['true', 'true']; 987 $g['stops'] = [['col' => $g['col'], 'opacity' => 1, 'offset' => 0], ['col' => $g['col2'], 'opacity' => 1, 'offset' => 1]]; 988 return $g; 989 } 990 return false; 991 } 992} 993