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