composer package updates
[openemr.git] / vendor / mpdf / mpdf / src / Gradient.php
blobfd288c10d6afdbe08cac579f018619ed2b0af638
1 <?php
3 namespace Mpdf;
5 use Mpdf\Color\ColorConverter;
7 class Gradient
9 const TYPE_LINEAR = 2;
10 const TYPE_RADIAL = 3;
12 /**
13 * @var \Mpdf\Mpdf
15 private $mpdf;
17 /**
18 * @var \Mpdf\SizeConverter
20 private $sizeConverter;
22 /**
23 * @var \Mpdf\Color\ColorConverter
25 private $colorConverter;
27 public function __construct(Mpdf $mpdf, SizeConverter $sizeConverter, ColorConverter $colorConverter)
29 $this->mpdf = $mpdf;
30 $this->sizeConverter = $sizeConverter;
31 $this->colorConverter = $colorConverter;
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)
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;
56 if ($patch_array[$i]['points'][$j] < 0) {
57 $patch_array[$i]['points'][$j] = 0;
59 if ($patch_array[$i]['points'][$j] > $bpcd) {
60 $patch_array[$i]['points'][$j] = $bpcd;
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));
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;
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;
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()
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));
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));
111 $this->mpdf->gradients[$n]['trans'] = true;
112 $s .= ' /TGS' . $n . ' gs ';
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;
122 $this->mpdf->_out($s);
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)
135 if (stripos($type, 'L') === 0) {
136 $type = self::TYPE_LINEAR;
137 } elseif (stripos($type, 'R') === 0) {
138 $type = self::TYPE_RADIAL;
141 if ($colorspace !== 'CMYK' && $colorspace !== 'Gray') {
142 $colorspace = 'RGB';
144 $bboxw = $w;
145 $bboxh = $h;
146 $usex = $x;
147 $usey = $y;
148 $usew = $bboxw;
149 $useh = $bboxh;
151 if ($type < 1) {
152 $type = self::TYPE_LINEAR;
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;
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);
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;
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;
192 } elseif ($angle == 180) {
193 if ($coords[4] == 0) {
194 $coords[2] = -1;
195 } else {
196 $coords[2] = 0;
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;
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));
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));
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));
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));
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;
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;
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));
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;
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));
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;
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;
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;
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)
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];
391 if ($coords[3] == false) {
392 $coords[3] = $coords[1];
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;
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)
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";
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;
467 if (count($stops) == 1) {
468 $stops[1] = $stops[0];
470 if (!isset($stops[0]['offset'])) {
471 $stops[0]['offset'] = 0;
473 if (!isset($stops[count($stops) - 1]['offset'])) {
474 $stops[count($stops) - 1]['offset'] = 1;
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
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;
494 if (isset($stops[0]['offset']) && $stops[0]['offset'] > 0) {
495 $firststop = $stops[0];
496 $firststop['offset'] = 0;
497 array_unshift($stops, $firststop);
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;
504 if ($stops[0]['offset'] > $stops[count($stops) - 1]['offset']) {
505 $stops[0]['offset'] = 0;
506 $stops[count($stops) - 1]['offset'] = 1;
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);
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;
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;
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;
544 $this->mpdf->gradients[$n]['stops'][$i]['offset'] = $stops[$i]['offset'];
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'];
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;
571 if ($trans) {
572 $this->mpdf->gradients[$n]['trans'] = true;
573 $s .= ' /TGS' . $n . ' gs ';
575 if (!is_array($extend) || count($extend) < 1) {
576 $extend = ['true', 'true']; // These are supposed to be quoted - appear in PDF file as text
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;
588 $this->mpdf->_out($s);
591 private function parseMozLinearGradient($m, $repeat)
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);
602 // Remove spaces inside e.g. rgb(x, x, x)
603 while (preg_match('/(\([^\)]*?)[ ]/', $v)) {
604 $v = preg_replace('/(\([^\)]*?)[ ]/', '\\1', $v);
606 $bgr = preg_split('/\s*,\s*/', $v);
607 for ($i = 0; $i < count($bgr); $i++) {
608 $bgr[$i] = preg_replace('/@/', ',', $bgr[$i]);
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;
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);
633 while ($angle < 0) {
634 $angle += 360;
636 $angle %= 360;
637 } elseif (trim($first[count($first) - 1]) === '0') {
638 $angle = 0;
640 if (stripos($bgr[0], 'left') !== false) {
641 $startx = 0;
642 } elseif (stripos($bgr[0], 'right') !== false) {
643 $startx = 1;
645 if (stripos($bgr[0], 'top') !== false) {
646 $starty = 1;
647 } elseif (stripos($bgr[0], 'bottom') !== false) {
648 $starty = 0;
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];
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];
667 if (isset($startx) && !isset($starty)) {
668 $starty = 0.5;
670 if (!isset($startx) && isset($starty)) {
671 $startx = 0.5;
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;
682 if (!isset($startx)) {
683 $startx = false;
685 if (!isset($starty)) {
686 $starty = false;
688 if (!isset($endx)) {
689 $endx = false;
691 if (!isset($endy)) {
692 $endy = false;
694 if (!isset($angle)) {
695 $angle = false;
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);
707 if ($col{0} == 1) {
708 $g['colorspace'] = 'Gray';
709 } elseif ($col{0} == 4 || $col{0} == 6) {
710 $g['colorspace'] = 'CMYK';
713 $g['stops'][] = $this->getStop($col, $el, true);
715 return $g;
718 private function parseMozRadialGradient($m, $repeat)
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);
729 // Remove spaces inside e.g. rgb(x, x, x)
730 while (preg_match('/(\([^\)]*?)[ ]/', $v)) {
731 $v = preg_replace('/(\([^\)]*?)[ ]/', '\\1', $v);
733 $bgr = preg_split('/\s*,\s*/', $v);
734 for ($i = 0; $i < count($bgr); $i++) {
735 $bgr[$i] = preg_replace('/@/', ',', $bgr[$i]);
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];
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];
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;
770 if (stripos($pos_angle, 'top') !== false) {
771 $starty = 1;
772 } elseif (stripos($pos_angle, 'bottom') !== false) {
773 $starty = 0;
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];
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];
793 if (!isset($starty)) {
794 $starty = 0.5;
796 if (!isset($startx)) {
797 $startx = 0.5;
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;
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];
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';
826 if (!isset($startx)) {
827 $startx = false;
829 if (!isset($starty)) {
830 $starty = false;
832 if (!isset($endx)) {
833 $endx = false;
835 if (!isset($endy)) {
836 $endy = false;
838 $radius = false;
839 $angle = 0;
840 $g['coords'] = [$startx, $starty, $endx, $endy, $radius, $angle, $shape, $size, $repeat];
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);
851 if ($col{0} == 1) {
852 $g['colorspace'] = 'Gray';
853 } elseif ($col{0} == 4 || $col{0} == 6) {
854 $g['colorspace'] = 'CMYK';
856 $g['stops'][] = $this->getStop($col, $el);
858 return $g;
861 private function getStop($col, $el, $convertOffset = false)
863 $stop = [
864 'col' => $col,
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;
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']);
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];
890 } else {
891 $stop['offset'] = $el[1];
896 return $stop;
899 public function parseMozGradient($bg)
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;
905 if (preg_match('/linear-gradient\((.*)\)/', $bg, $m)) {
906 $g = $this->parseMozLinearGradient($m, $repeat);
907 if (count($g['stops'])) {
908 return $g;
910 } elseif (preg_match('/radial-gradient\((.*)\)/', $bg, $m)) {
911 $g = $this->parseMozRadialGradient($m, $repeat);
912 if (count($g['stops'])) {
913 return $g;
916 return [];
919 public function parseBackgroundGradient($bg)
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;
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]];
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';
950 if ($cor) {
951 $g['col'] = $cor;
952 } else {
953 $g['col'] = $this->colorConverter->convert(255, $this->mpdf->PDFAXwarnings);
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);
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;
965 return false;