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