1<?php
2
3namespace Mpdf\Image;
4
5use Mpdf\Color\ColorConverter;
6use Mpdf\Css\TextVars;
7use Mpdf\CssManager;
8use Mpdf\Language\LanguageToFontInterface;
9use Mpdf\Language\ScriptToLanguageInterface;
10use Mpdf\Mpdf;
11use Mpdf\Otl;
12use Mpdf\SizeConverter;
13use Mpdf\Ucdn;
14use Mpdf\Utils\Arrays;
15use Mpdf\Utils\UtfString;
16
17/**
18 * SVG class modified for mPDF version >= 6.0
19 *
20 * Works in pixels as main units - converting to PDF units when outputing to PDF string and on returning size
21 *
22 * @author Ian Back
23 * @author sylvain briand (syb@godisaduck.com), modified by rick trevino (rtrevino1@yahoo.com)
24 *
25 * @link http://www.godisaduck.com/svg2pdf_with_fpdf
26 * @link http://rhodopsin.blogspot.com
27 */
28class Svg
29{
30
31	/**
32	 * ATM marked as public in spite of xml handling callbacks
33	 *
34	 * @var \Mpdf\Mpdf
35	 */
36	public $mpdf;
37
38	/**
39	 * @var \Mpdf\Otl
40	 */
41	public $otl;
42
43	/**
44	 * @var \Mpdf\CssManager
45	 */
46	public $cssManager;
47
48	/**
49	 * @var \Mpdf\SizeConverter
50	 */
51	public $sizeConverter;
52
53	/**
54	 * @var \Mpdf\Color\ColorConverter
55	 */
56	public $colorConverter;
57
58	/**
59	 * @var \Mpdf\Language\LanguageToFontInterface
60	 */
61	public $languageToFont;
62
63	/**
64	 * @var \Mpdf\Language\ScriptToLanguageInterface
65	 */
66	public $scriptToLanguage;
67
68	/**
69	 * @var \Mpdf\Image\ImageProcessor
70	 */
71	private $imageProcessor;
72
73	/**
74	 * Holds content of SVG fonts defined in image
75	 *
76	 * @var array
77	 */
78	var $svg_font;
79
80	/**
81	 * contient les infos sur les gradient fill du svg classé par id du svg
82	 *
83	 * @var array
84	 */
85	var $svg_gradient;
86
87	/**
88	 * contient les ids des objet shading
89	 *
90	 * @var array
91	 */
92	var $svg_shadinglist;
93
94	/**
95	 * contenant les infos du svg voulue par l'utilisateur
96	 *
97	 * @var array
98	 */
99	var $svg_info;
100
101	/**
102	 * holds all attributes of root <svg> tag
103	 *
104	 * @var array
105	 */
106	var $svg_attribs;
107
108	/**
109	 * contenant les style de groupes du svg
110	 *
111	 * @var array
112	 */
113	var $svg_style;
114
115	/**
116	 * contenant le tracage du svg en lui même.
117	 *
118	 * @var string
119	 */
120	var $svg_string;
121
122	/**
123	 * holds string info to write txt to image
124	 *
125	 * @var string
126	 */
127	var $txt_data;
128
129	/**
130	 * @var array
131	 */
132	var $txt_style;
133
134	var $xbase;
135
136	var $ybase;
137
138	var $svg_error;
139
140	var $subPathInit;
141
142	var $spxstart;
143
144	var $spystart;
145
146	var $kp; // convert pixels to PDF units
147
148	var $pathBBox;
149
150	var $textlength; // mPDF 5.7.4
151
152	var $texttotallength; // mPDF 5.7.4
153
154	var $textoutput; // mPDF 5.7.4
155
156	var $textanchor; // mPDF 5.7.4
157
158	var $textXorigin; // mPDF 5.7.4
159
160	var $textYorigin; // mPDF 5.7.4
161
162	var $textjuststarted; // mPDF 5.7.4
163
164	var $intext;  // mPDF 5.7.4
165
166	private $dashesUsed;
167
168	private $kf;
169
170	private $lastcommand;
171
172	private $lastcontrolpoints;
173
174	private $inDefs;
175
176	public function __construct(
177		Mpdf $mpdf,
178		Otl $otl,
179		CssManager $cssManager,
180		ImageProcessor $imageProcessor,
181		SizeConverter $sizeConverter,
182		ColorConverter $colorConverter,
183		LanguageToFontInterface $languageToFont,
184		ScriptToLanguageInterface $scriptToLanguage
185	) {
186
187		$this->mpdf = $mpdf;
188		$this->otl = $otl;
189		$this->cssManager = $cssManager;
190		$this->imageProcessor = $imageProcessor;
191		$this->sizeConverter = $sizeConverter;
192		$this->colorConverter = $colorConverter;
193		$this->languageToFont = $languageToFont;
194		$this->scriptToLanguage = $scriptToLanguage;
195
196		$this->svg_font = []; // mPDF 6
197		$this->svg_gradient = [];
198		$this->svg_shadinglist = [];
199		$this->txt_data = [];
200		$this->svg_string = '';
201		$this->svg_info = [];
202		$this->svg_attribs = [];
203		$this->xbase = 0;
204		$this->ybase = 0;
205		$this->svg_error = false;
206		$this->subPathInit = false;
207		$this->dashesUsed = false;
208
209		$this->textlength = 0; // mPDF 5.7.4
210		$this->texttotallength = 0; // mPDF 5.7.4
211		$this->textoutput = ''; // mPDF 5.7.4
212		$this->textanchor = 'start'; // mPDF 5.7.4
213		$this->textXorigin = 0; // mPDF 5.7.4
214		$this->textYorigin = 0; // mPDF 5.7.4
215		$this->textjuststarted = false; // mPDF 5.7.4
216		$this->intext = false; // mPDF 5.7.4
217
218		$this->kp = 72 / $mpdf->img_dpi; // constant To convert pixels to pts/PDF units
219		$this->kf = 1; // constant To convert font size if re-mapped
220		$this->pathBBox = [];
221
222		$this->svg_style = [
223			[
224				'fill' => 'black',
225				'fill-opacity' => 1, //	remplissage opaque par defaut
226				'fill-rule' => 'nonzero', //	mode de remplissage par defaut
227				'stroke' => 'none', //	pas de trait par defaut
228				'stroke-linecap' => 'butt', //	style de langle par defaut
229				'stroke-linejoin' => 'miter',
230				'stroke-miterlimit' => 4, //	limite de langle par defaut
231				'stroke-opacity' => 1, //	trait opaque par defaut
232				'stroke-width' => 1,
233				'stroke-dasharray' => 0,
234				'stroke-dashoffset' => 0,
235				'color' => ''
236			]
237		];
238
239		$this->txt_style = [
240			[
241				'fill' => 'black', //	pas de remplissage par defaut
242				'font-family' => $mpdf->default_font,
243				'font-size' => $mpdf->default_font_size, // 	****** this is pts
244				'font-weight' => 'normal', //	normal | bold
245				'font-style' => 'normal', //	italic | normal
246				'text-anchor' => 'start', // alignment: start, middle, end
247				'fill-opacity' => 1, //	remplissage opaque par defaut
248				'fill-rule' => 'nonzero', //	mode de remplissage par defaut
249				'stroke' => 'none', //	pas de trait par defaut
250				'stroke-opacity' => 1, //	trait opaque par defaut
251				'stroke-width' => 1,
252				'color' => ''
253			]
254		];
255	}
256
257	// mPDF 5.7.4 Embedded image
258	function svgImage($attribs)
259	{
260		// x and y are coordinates
261		$x = (isset($attribs['x']) ? $attribs['x'] : 0);
262		$y = (isset($attribs['y']) ? $attribs['y'] : 0);
263		// preserveAspectRatio
264		$par = (isset($attribs['preserveAspectRatio']) ? $attribs['preserveAspectRatio'] : 'xMidYMid meet');
265		// width and height are <lengths> - Required attributes
266		$wset = (isset($attribs['width']) ? $attribs['width'] : 0);
267		$hset = (isset($attribs['height']) ? $attribs['height'] : 0);
268		$w = $this->sizeConverter->convert($wset, $this->svg_info['w'] * (25.4 / $this->mpdf->dpi), $this->mpdf->FontSize, false);
269		$h = $this->sizeConverter->convert($hset, $this->svg_info['h'] * (25.4 / $this->mpdf->dpi), $this->mpdf->FontSize, false);
270		if ($w == 0 || $h == 0) {
271			return;
272		}
273		// Convert to pixels = SVG units
274		$w *= 1 / (25.4 / $this->mpdf->dpi);
275		$h *= 1 / (25.4 / $this->mpdf->dpi);
276
277		$srcpath = $attribs['xlink:href'];
278		$orig_srcpath = '';
279		if (trim($srcpath) != '' && substr($srcpath, 0, 4) == 'var:') {
280			$orig_srcpath = $srcpath;
281			$srcpath = $this->mpdf->GetFullPath($srcpath);
282		}
283
284		// Image file (does not allow vector images i.e. WMF/SVG)
285		// mPDF 6 Added $this->mpdf->interpolateImages
286		$info = $this->imageProcessor->getImage($srcpath, true, false, $orig_srcpath, $this->mpdf->interpolateImages);
287		if (!$info) {
288			return;
289		}
290
291		// x,y,w,h define the reference rectangle
292		$img_h = $h;
293		$img_w = $w;
294		$img_x = $x;
295		$img_y = $y;
296		$meetOrSlice = 'meet';
297
298		// preserveAspectRatio
299		$ar = preg_split('/\s+/', strtolower($par));
300		if ($ar[0] != 'none') { // If "none" need to do nothing
301			//  Force uniform scaling
302			if (isset($ar[1]) && $ar[1] == 'slice') {
303				$meetOrSlice = 'slice';
304			} else {
305				$meetOrSlice = 'meet';
306			}
307			if ($info['h'] / $info['w'] > $h / $w) {
308				if ($meetOrSlice == 'meet') { // the entire viewBox is visible within the viewport
309					$img_w = $img_h * $info['w'] / $info['h'];
310				} else { // the entire viewport is covered by the viewBox
311					$img_h = $img_w * $info['h'] / $info['w'];
312				}
313			} elseif ($info['h'] / $info['w'] < $h / $w) {
314				if ($meetOrSlice == 'meet') { // the entire viewBox is visible within the viewport
315					$img_h = $img_w * $info['h'] / $info['w'];
316				} else { // the entire viewport is covered by the viewBox
317					$img_w = $img_h * $info['w'] / $info['h'];
318				}
319			}
320			if ($ar[0] == 'xminymin') {
321				// do nothing to x
322				// do nothing to y
323			} elseif ($ar[0] == 'xmidymin') {
324				$img_x += $w / 2 - $img_w / 2; // xMid
325				// do nothing to y
326			} elseif ($ar[0] == 'xmaxymin') {
327				$img_x += $w - $img_w; // xMax
328				// do nothing to y
329			} elseif ($ar[0] == 'xminymid') {
330				// do nothing to x
331				$img_y += $h / 2 - $img_h / 2; // yMid
332			} elseif ($ar[0] == 'xmaxymid') {
333				$img_x += $w - $img_w; // xMax
334				$img_y += $h / 2 - $img_h / 2; // yMid
335			} elseif ($ar[0] == 'xminymax') {
336				// do nothing to x
337				$img_y += $h - $img_h; // yMax
338			} elseif ($ar[0] == 'xmidymax') {
339				$img_x += $w / 2 - $img_w / 2; // xMid
340				$img_y += $h - $img_h; // yMax
341			} elseif ($ar[0] == 'xmaxymax') {
342				$img_x += $w - $img_w; // xMax
343				$img_y += $h - $img_h; // yMax
344			} else { // xMidYMid (the default)
345				$img_x += $w / 2 - $img_w / 2; // xMid
346				$img_y += $h / 2 - $img_h / 2; // yMid
347			}
348		}
349
350		// Output
351		if ($meetOrSlice == 'slice') { // need to add a clipping path to reference rectangle
352			$s = ' q 0 w '; // Line width=0
353			$s .= sprintf('%.3F %.3F m ', ($x) * $this->kp, (-($y + $h)) * $this->kp); // start point TL before the arc
354			$s .= sprintf('%.3F %.3F l ', ($x) * $this->kp, (-($y)) * $this->kp); // line to BL
355			$s .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-($y)) * $this->kp); // line to BR
356			$s .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-($y + $h)) * $this->kp); // line to TR
357			$s .= sprintf('%.3F %.3F l ', ($x) * $this->kp, (-($y + $h)) * $this->kp); // line to TL
358			$s .= ' W n '; // Ends path no-op & Sets the clipping path
359			$this->svgWriteString($s);
360		}
361
362		$outstring = sprintf(" q %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q ", $img_w * $this->kp, $img_h * $this->kp, $img_x * $this->kp, -($img_y + $img_h) * $this->kp, $info['i']);
363		$this->svgWriteString($outstring);
364
365		if ($meetOrSlice == 'slice') { // need to end clipping path
366			$this->svgWriteString(' Q ');
367		}
368	}
369
370	function svgGradient($gradient_info, $attribs, $element)
371	{
372		$n = count($this->mpdf->gradients) + 1;
373
374		// Get bounding dimensions of element
375		$w = 100;
376		$h = 100;
377		$x_offset = 0;
378		$y_offset = 0;
379		if ($element == 'rect') {
380			$w = $attribs['width'];
381			$h = $attribs['height'];
382			$x_offset = $attribs['x'];
383			$y_offset = $attribs['y'];
384		} elseif ($element == 'ellipse') {
385			$w = $attribs['rx'] * 2;
386			$h = $attribs['ry'] * 2;
387			$x_offset = $attribs['cx'] - $attribs['rx'];
388			$y_offset = $attribs['cy'] - $attribs['ry'];
389		} elseif ($element == 'circle') {
390			$w = $attribs['r'] * 2;
391			$h = $attribs['r'] * 2;
392			$x_offset = $attribs['cx'] - $attribs['r'];
393			$y_offset = $attribs['cy'] - $attribs['r'];
394		} elseif ($element == 'polygon') {
395			$pts = preg_split('/[ ,]+/', trim($attribs['points']));
396			$maxr = $maxb = 0;
397			$minl = $mint = 999999;
398			for ($i = 0; $i < count($pts); $i++) {
399				if ($i % 2 == 0) { // x values
400					$minl = min($minl, $pts[$i]);
401					$maxr = max($maxr, $pts[$i]);
402				} else { // y values
403					$mint = min($mint, $pts[$i]);
404					$maxb = max($maxb, $pts[$i]);
405				}
406			}
407			$w = $maxr - $minl;
408			$h = $maxb - $mint;
409			$x_offset = $minl;
410			$y_offset = $mint;
411		} elseif ($element == 'path') {
412			if (is_array($this->pathBBox) && $this->pathBBox[2] > 0) {
413				$w = $this->pathBBox[2];
414				$h = $this->pathBBox[3];
415				$x_offset = $this->pathBBox[0];
416				$y_offset = $this->pathBBox[1];
417			} else {
418				preg_match_all('/([a-z]|[A-Z])([ ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER);
419				$maxr = $maxb = 0;
420				$minl = $mint = 999999;
421				foreach ($commands as $c) {
422					if (count($c) == 3) {
423						list($tmp, $cmd, $arg) = $c;
424						if ($cmd == 'M' || $cmd == 'L' || $cmd == 'C' || $cmd == 'S' || $cmd == 'Q' || $cmd == 'T') {
425							$pts = preg_split('/[ ,]+/', trim($arg));
426							for ($i = 0; $i < count($pts); $i++) {
427								if ($i % 2 == 0) { // x values
428									$minl = min($minl, $pts[$i]);
429									$maxr = max($maxr, $pts[$i]);
430								} else { // y values
431									$mint = min($mint, $pts[$i]);
432									$maxb = max($maxb, $pts[$i]);
433								}
434							}
435						}
436						if ($cmd == 'H') { // sets new x
437							$minl = min($minl, $arg);
438							$maxr = max($maxr, $arg);
439						}
440						if ($cmd == 'V') { // sets new y
441							$mint = min($mint, $arg);
442							$maxb = max($maxb, $arg);
443						}
444					}
445				}
446				$w = $maxr - $minl;
447				$h = $maxb - $mint;
448				$x_offset = $minl;
449				$y_offset = $mint;
450			}
451		}
452		if (!$w || $w == -999999) {
453			$w = 100;
454		}
455		if (!$h || $h == -999999) {
456			$h = 100;
457		}
458		if ($x_offset == 999999) {
459			$x_offset = 0;
460		}
461		if ($y_offset == 999999) {
462			$y_offset = 0;
463		}
464
465		// TRANSFORMATIONS
466		$transformations = '';
467		if (isset($gradient_info['transform'])) {
468			preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)\((.*?)\)/is', $gradient_info['transform'], $m);
469			if (count($m[0])) {
470				for ($i = 0; $i < count($m[0]); $i++) {
471					$c = strtolower($m[1][$i]);
472					$v = trim($m[2][$i]);
473					$vv = preg_split('/[ ,]+/', $v);
474					if ($c == 'matrix' && count($vv) == 6) {
475						// Note angle of rotation is reversed (from SVG to PDF), so vv[1] and vv[2] are negated
476						// cf svgDefineStyle()
477						$transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $vv[0], -$vv[1], -$vv[2], $vv[3], $vv[4] * $this->kp, -$vv[5] * $this->kp);
478					} elseif ($c == 'translate' && count($vv)) {
479						$tm[4] = $vv[0];
480						if (count($vv) == 2) {
481							$t_y = -$vv[1];
482						} else {
483							$t_y = 0;
484						}
485						$tm[5] = $t_y;
486						$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $tm[4] * $this->kp, $tm[5] * $this->kp);
487					} elseif ($c == 'scale' && count($vv)) {
488						if (count($vv) == 2) {
489							$s_y = $vv[1];
490						} else {
491							$s_y = $vv[0];
492						}
493						$tm[0] = $vv[0];
494						$tm[3] = $s_y;
495						$transformations .= sprintf(' %.3F 0 0 %.3F 0 0 cm ', $tm[0], $tm[3]);
496					} elseif ($c == 'rotate' && count($vv)) {
497						$tm[0] = cos(deg2rad(-$vv[0]));
498						$tm[1] = sin(deg2rad(-$vv[0]));
499						$tm[2] = -$tm[1];
500						$tm[3] = $tm[0];
501						if (count($vv) == 3) {
502							$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $vv[1] * $this->kp, -$vv[2] * $this->kp);
503						}
504						$transformations .= sprintf(' %.3F %.3F %.3F %.3F 0 0 cm ', $tm[0], $tm[1], $tm[2], $tm[3]);
505						if (count($vv) == 3) {
506							$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', -$vv[1] * $this->kp, $vv[2] * $this->kp);
507						}
508					} elseif ($c == 'skewx' && count($vv)) {
509						$tm[2] = tan(deg2rad(-$vv[0]));
510						$transformations .= sprintf(' 1 0 %.3F 1 0 0 cm ', $tm[2]);
511					} elseif ($c == 'skewy' && count($vv)) {
512						$tm[1] = tan(deg2rad(-$vv[0]));
513						$transformations .= sprintf(' 1 %.3F 0 1 0 0 cm ', $tm[1]);
514					}
515				}
516			}
517		}
518
519
520		$return = "";
521
522		if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') {
523			if ($transformations) {
524				$return .= $transformations;
525			}
526		}
527		$spread = 'P';  // pad
528		if (isset($gradient_info['spread'])) {
529			if (strtolower($gradient_info['spread']) == 'reflect') {
530				$spread = 'F';
531			} // reflect
532			elseif (strtolower($gradient_info['spread']) == 'repeat') {
533				$spread = 'R';
534			} // repeat
535		}
536
537
538		for ($i = 0; $i < (count($gradient_info['color'])); $i++) {
539			if (stristr($gradient_info['color'][$i]['offset'], '%') !== false) {
540				$gradient_info['color'][$i]['offset'] = ((float) $gradient_info['color'][$i]['offset']) / 100;
541			}
542			if (isset($gradient_info['color'][($i + 1)]['offset']) && stristr($gradient_info['color'][($i + 1)]['offset'], '%') !== false) {
543				$gradient_info['color'][($i + 1)]['offset'] = ((float) $gradient_info['color'][($i + 1)]['offset']) / 100;
544			}
545			if ($gradient_info['color'][$i]['offset'] < 0) {
546				$gradient_info['color'][$i]['offset'] = 0;
547			}
548			if ($gradient_info['color'][$i]['offset'] > 1) {
549				$gradient_info['color'][$i]['offset'] = 1;
550			}
551			if ($i > 0) {
552				if ($gradient_info['color'][$i]['offset'] < $gradient_info['color'][($i - 1)]['offset']) {
553					$gradient_info['color'][$i]['offset'] = $gradient_info['color'][($i - 1)]['offset'];
554				}
555			}
556		}
557
558		if (isset($gradient_info['color'][0]['offset']) && $gradient_info['color'][0]['offset'] > 0) {
559			array_unshift($gradient_info['color'], $gradient_info['color'][0]);
560			$gradient_info['color'][0]['offset'] = 0;
561		}
562		$ns = count($gradient_info['color']);
563		if (isset($gradient_info['color'][($ns - 1)]['offset']) && $gradient_info['color'][($ns - 1)]['offset'] < 1) {
564			$gradient_info['color'][] = $gradient_info['color'][($ns - 1)];
565			$gradient_info['color'][($ns)]['offset'] = 1;
566		}
567		$ns = count($gradient_info['color']);
568
569
570
571
572		if ($gradient_info['type'] == 'linear') {
573			// mPDF 4.4.003
574			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') {
575				if (isset($gradient_info['info']['x1'])) {
576					$gradient_info['info']['x1'] = ($gradient_info['info']['x1'] - $x_offset) / $w;
577				}
578				if (isset($gradient_info['info']['y1'])) {
579					$gradient_info['info']['y1'] = ($gradient_info['info']['y1'] - $y_offset) / $h;
580				}
581				if (isset($gradient_info['info']['x2'])) {
582					$gradient_info['info']['x2'] = ($gradient_info['info']['x2'] - $x_offset) / $w;
583				}
584				if (isset($gradient_info['info']['y2'])) {
585					$gradient_info['info']['y2'] = ($gradient_info['info']['y2'] - $y_offset) / $h;
586				}
587			}
588			if (isset($gradient_info['info']['x1'])) {
589				$x1 = $gradient_info['info']['x1'];
590			} else {
591				$x1 = 0;
592			}
593			if (isset($gradient_info['info']['y1'])) {
594				$y1 = $gradient_info['info']['y1'];
595			} else {
596				$y1 = 0;
597			}
598			if (isset($gradient_info['info']['x2'])) {
599				$x2 = $gradient_info['info']['x2'];
600			} else {
601				$x2 = 1;
602			}
603			if (isset($gradient_info['info']['y2'])) {
604				$y2 = $gradient_info['info']['y2'];
605			} else {
606				$y2 = 0;
607			} // mPDF 6
608
609			if (strpos($x1, '%') !== false) {
610				$x1 = (stristr($x1, '%', true) + 0) / 100;
611			}
612			if (strpos($x2, '%') !== false) {
613				$x2 = (stristr($x2, '%', true) + 0) / 100;
614			}
615			if (strpos($y1, '%') !== false) {
616				$y1 = (stristr($y1, '%', true) + 0) / 100;
617			}
618			if (strpos($y2, '%') !== false) {
619				$y2 = (stristr($y2, '%', true) + 0) / 100;
620			}
621
622			// mPDF 5.0.042
623			$bboxw = $w;
624			$bboxh = $h;
625			$usex = $x_offset;
626			$usey = $y_offset;
627			$usew = $bboxw;
628			$useh = $bboxh;
629			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') {
630				$angle = rad2deg(atan2(($gradient_info['info']['y2'] - $gradient_info['info']['y1']), ($gradient_info['info']['x2'] - $gradient_info['info']['x1'])));
631				if ($angle < 0) {
632					$angle += 360;
633				} elseif ($angle > 360) {
634					$angle -= 360;
635				}
636				if ($angle != 0 && $angle != 360 && $angle != 90 && $angle != 180 && $angle != 270) {
637					if ($w >= $h) {
638						$y1 *= $h / $w;
639						$y2 *= $h / $w;
640						$usew = $useh = $bboxw;
641					} else {
642						$x1 *= $w / $h;
643						$x2 *= $w / $h;
644						$usew = $useh = $bboxh;
645					}
646				}
647			}
648			$a = $usew;  // width
649			$d = -$useh; // height
650			$e = $usex;  // x- offset
651			$f = -$usey; // -y-offset
652
653			$return .= sprintf('%.3F 0 0 %.3F %.3F %.3F cm ', $a * $this->kp, $d * $this->kp, $e * $this->kp, $f * $this->kp);
654
655			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'objectboundingbox') {
656				if ($transformations) {
657					$return .= $transformations;
658				}
659			}
660
661			$trans = false;
662
663			if ($spread == 'R' || $spread == 'F') { // Repeat  /  Reflect
664
665				$offs = [];
666				for ($i = 0; $i < $ns; $i++) {
667					$offs[$i] = $gradient_info['color'][$i]['offset'];
668				}
669
670				$gp = 0;
671				$inside = true;
672
673				while ($inside) {
674					$gp++;
675					for ($i = 0; $i < $ns; $i++) {
676						if ($spread == 'F' && ($gp % 2) == 1) { // Reflect
677							$gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][(($ns * ($gp - 1)) + ($ns - $i - 1))];
678							$tmp = $gp + (1 - $offs[($ns - $i - 1)]);
679							$gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp;
680						} else { // Reflect
681							$gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][$i];
682							$tmp = $gp + $offs[$i];
683							$gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp;
684						}
685						// IF STILL INSIDE BOX OR STILL VALID
686						// Point on axis to test
687						$px1 = $x1 + ($x2 - $x1) * $tmp;
688						$py1 = $y1 + ($y2 - $y1) * $tmp;
689						// Get perpendicular axis
690						$alpha = atan2($y2 - $y1, $x2 - $x1);
691						$alpha += M_PI / 2; // rotate 90 degrees
692						// Get arbitrary point to define line perpendicular to axis
693						$px2 = $px1 + cos($alpha);
694						$py2 = $py1 + sin($alpha);
695
696						$res1 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 0, 1); // $x=0 vert axis
697						$res2 = $this->testIntersect($px1, $py1, $px2, $py2, 1, 0, 1, 1); // $x=1 vert axis
698						$res3 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 1, 0); // $y=0 horiz axis
699						$res4 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 1, 1, 1); // $y=1 horiz axis
700
701						if (!$res1 && !$res2 && !$res3 && !$res4) {
702							$inside = false;
703						}
704					}
705				}
706
707				$inside = true;
708				$gp = 0;
709				while ($inside) {
710					$gp++;
711					$newarr = [];
712					for ($i = 0; $i < $ns; $i++) {
713						if ($spread == 'F') { // Reflect
714							$newarr[$i] = $gradient_info['color'][($ns - $i - 1)];
715							if (($gp % 2) == 1) {
716								$tmp = -$gp + (1 - $offs[($ns - $i - 1)]);
717								$newarr[$i]['offset'] = $tmp;
718							} else {
719								$tmp = -$gp + $offs[$i];
720								$newarr[$i]['offset'] = $tmp;
721							}
722						} else { // Reflect
723							$newarr[$i] = $gradient_info['color'][$i];
724							$tmp = -$gp + $offs[$i];
725							$newarr[$i]['offset'] = $tmp;
726						}
727
728						// IF STILL INSIDE BOX OR STILL VALID
729						// Point on axis to test
730						$px1 = $x1 + ($x2 - $x1) * $tmp;
731						$py1 = $y1 + ($y2 - $y1) * $tmp;
732						// Get perpendicular axis
733						$alpha = atan2($y2 - $y1, $x2 - $x1);
734						$alpha += M_PI / 2; // rotate 90 degrees
735						// Get arbitrary point to define line perpendicular to axis
736						$px2 = $px1 + cos($alpha);
737						$py2 = $py1 + sin($alpha);
738
739						$res1 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 0, 1); // $x=0 vert axis
740						$res2 = $this->testIntersect($px1, $py1, $px2, $py2, 1, 0, 1, 1); // $x=1 vert axis
741						$res3 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 1, 0); // $y=0 horiz axis
742						$res4 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 1, 1, 1); // $y=1 horiz axis
743						if (!$res1 && !$res2 && !$res3 && !$res4) {
744							$inside = false;
745						}
746					}
747					for ($i = ($ns - 1); $i >= 0; $i--) {
748						if (isset($newarr[$i]['offset'])) {
749							array_unshift($gradient_info['color'], $newarr[$i]);
750						}
751					}
752				}
753			}
754
755			// Gradient STOPs
756			$stops = count($gradient_info['color']);
757			if ($stops < 2) {
758				return '';
759			}
760
761			$range = $gradient_info['color'][count($gradient_info['color']) - 1]['offset'] - $gradient_info['color'][0]['offset'];
762			$min = $gradient_info['color'][0]['offset'];
763
764			for ($i = 0; $i < ($stops); $i++) {
765				if (!$gradient_info['color'][$i]['color']) {
766					if ($gradient_info['colorspace'] == 'RGB') {
767						$gradient_info['color'][$i]['color'] = '0 0 0';
768					} elseif ($gradient_info['colorspace'] == 'Gray') {
769						$gradient_info['color'][$i]['color'] = '0';
770					} elseif ($gradient_info['colorspace'] == 'CMYK') {
771						$gradient_info['color'][$i]['color'] = '1 1 1 1';
772					}
773				}
774				$offset = ($gradient_info['color'][$i]['offset'] - $min) / $range;
775				$this->mpdf->gradients[$n]['stops'][] = [
776					'col' => $gradient_info['color'][$i]['color'],
777					'opacity' => $gradient_info['color'][$i]['opacity'],
778					'offset' => $offset];
779				if ($gradient_info['color'][$i]['opacity'] < 1) {
780					$trans = true;
781				}
782			}
783			$grx1 = $x1 + ($x2 - $x1) * $gradient_info['color'][0]['offset'];
784			$gry1 = $y1 + ($y2 - $y1) * $gradient_info['color'][0]['offset'];
785			$grx2 = $x1 + ($x2 - $x1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset'];
786			$gry2 = $y1 + ($y2 - $y1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset'];
787
788			$this->mpdf->gradients[$n]['coords'] = [$grx1, $gry1, $grx2, $gry2];
789
790			$this->mpdf->gradients[$n]['colorspace'] = $gradient_info['colorspace'];
791
792			$this->mpdf->gradients[$n]['type'] = 2;
793			$this->mpdf->gradients[$n]['fo'] = true;
794
795			$this->mpdf->gradients[$n]['extend'] = ['true', 'true'];
796			if ($trans) {
797				$this->mpdf->gradients[$n]['trans'] = true;
798				$return .= ' /TGS' . ($n) . ' gs ';
799			}
800			$return .= ' /Sh' . ($n) . ' sh ';
801			$return .= " Q\n";
802		} elseif ($gradient_info['type'] == 'radial') {
803			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') {
804				if ($w > $h) {
805					$h = $w;
806				} else {
807					$w = $h;
808				}
809				if (isset($gradient_info['info']['x0'])) {
810					$gradient_info['info']['x0'] = ($gradient_info['info']['x0'] - $x_offset) / $w;
811				}
812				if (isset($gradient_info['info']['y0'])) {
813					$gradient_info['info']['y0'] = ($gradient_info['info']['y0'] - $y_offset) / $h;
814				}
815				if (isset($gradient_info['info']['x1'])) {
816					$gradient_info['info']['x1'] = ($gradient_info['info']['x1'] - $x_offset) / $w;
817				}
818				if (isset($gradient_info['info']['y1'])) {
819					$gradient_info['info']['y1'] = ($gradient_info['info']['y1'] - $y_offset) / $h;
820				}
821				if (isset($gradient_info['info']['r'])) {
822					$gradient_info['info']['rx'] = $gradient_info['info']['r'] / $w;
823				}
824				if (isset($gradient_info['info']['r'])) {
825					$gradient_info['info']['ry'] = $gradient_info['info']['r'] / $h;
826				}
827			}
828
829			if (isset($gradient_info['info']['x0'])) {
830				$x0 = $gradient_info['info']['x0'];
831			} else {
832				$x0 = 0.5;
833			}
834			if (isset($gradient_info['info']['y0'])) {
835				$y0 = $gradient_info['info']['y0'];
836			} else {
837				$y0 = 0.5;
838			}
839			if (isset($gradient_info['info']['rx'])) {
840				$rx = $gradient_info['info']['rx'];
841			} elseif (isset($gradient_info['info']['r'])) {
842				$rx = $gradient_info['info']['r'];
843			} else {
844				$rx = 0.5;
845			}
846			if (isset($gradient_info['info']['ry'])) {
847				$ry = $gradient_info['info']['ry'];
848			} elseif (isset($gradient_info['info']['r'])) {
849				$ry = $gradient_info['info']['r'];
850			} else {
851				$ry = 0.5;
852			}
853			if (isset($gradient_info['info']['x1'])) {
854				$x1 = $gradient_info['info']['x1'];
855			} else {
856				$x1 = $x0;
857			}
858			if (isset($gradient_info['info']['y1'])) {
859				$y1 = $gradient_info['info']['y1'];
860			} else {
861				$y1 = $y0;
862			}
863
864			if (strpos($x1, '%') !== false) {
865				$x1 = (stristr($x1, '%', true) + 0) / 100;
866			}
867			if (strpos($x0, '%') !== false) {
868				$x0 = (stristr($x0, '%', true) + 0) / 100;
869			}
870			if (strpos($y1, '%') !== false) {
871				$y1 = (stristr($y1, '%', true) + 0) / 100;
872			}
873			if (strpos($y0, '%') !== false) {
874				$y0 = (stristr($y0, '%', true) + 0) / 100;
875			}
876			if (strpos($rx, '%') !== false) {
877				$rx = (stristr($rx, '%', true) + 0) / 100;
878			}
879			if (strpos($ry, '%') !== false) {
880				$ry = (stristr($ry, '%', true) + 0) / 100;
881			}
882
883			$bboxw = $w;
884			$bboxh = $h;
885			$usex = $x_offset;
886			$usey = $y_offset;
887			$usew = $bboxw;
888			$useh = $bboxh;
889
890			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') {
891
892				$ay1 = isset($gradient_info['info']['y1']) ? $gradient_info['info']['y1'] : 0;
893				$ax1 = isset($gradient_info['info']['x1']) ? $gradient_info['info']['x1'] : 0;
894
895				$angle = rad2deg(atan2(
896					($gradient_info['info']['y0'] - $ay1),
897					($gradient_info['info']['x0'] - $ax1)
898				));
899
900				if ($angle < 0) {
901					$angle += 360;
902				} elseif ($angle > 360) {
903					$angle -= 360;
904				}
905
906				if ($angle != 0 && $angle != 360 && $angle != 90 && $angle != 180 && $angle != 270) {
907					if ($w >= $h) {
908						$y1 *= $h / $w;
909						$y0 *= $h / $w;
910						$rx *= $h / $w;
911						$ry *= $h / $w;
912						$usew = $useh = $bboxw;
913					} else {
914						$x1 *= $w / $h;
915						$x0 *= $w / $h;
916						$rx *= $w / $h;
917						$ry *= $w / $h;
918						$usew = $useh = $bboxh;
919					}
920				}
921			}
922
923			$a = $usew;  // width
924			$d = -$useh; // height
925			$e = $usex;  // x- offset
926			$f = -$usey; // -y-offset
927
928			$r = $rx;
929
930			$return .= sprintf('%.3F 0 0 %.3F %.3F %.3F cm ', $a * $this->kp, $d * $this->kp, $e * $this->kp, $f * $this->kp);
931
932			// mPDF 5.0.039
933			if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'objectboundingbox') {
934				if ($transformations) {
935					$return .= $transformations;
936				}
937			}
938
939			// mPDF 5.7.4
940			// x1 and y1 (fx, fy) should be inside the circle defined by x0 y0 (cx, cy)
941			// "If the point defined by fx and fy lies outside the circle defined by cx, cy and r, then the user agent shall set
942			// the focal point to the intersection of the line from (cx, cy) to (fx, fy) with the circle defined by cx, cy and r."
943			while (pow(($x1 - $x0), 2) + pow(($y1 - $y0), 2) >= pow($r, 2)) {
944				// Gradually move along fx,fy towards cx,cy in 100'ths until meets criteria
945				$x1 -= ($x1 - $x0) / 100;
946				$y1 -= ($y1 - $y0) / 100;
947			}
948
949
950			if ($spread == 'R' || $spread == 'F') { // Repeat  /  Reflect
951				$offs = [];
952				for ($i = 0; $i < $ns; $i++) {
953					$offs[$i] = $gradient_info['color'][$i]['offset'];
954				}
955				$gp = 0;
956				$inside = true;
957				while ($inside) {
958					$gp++;
959					for ($i = 0; $i < $ns; $i++) {
960						if ($spread == 'F' && ($gp % 2) == 1) { // Reflect
961							$gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][(($ns * ($gp - 1)) + ($ns - $i - 1))];
962							$tmp = $gp + (1 - $offs[($ns - $i - 1)]);
963							$gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp;
964						} else { // Reflect
965							$gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][$i];
966							$tmp = $gp + $offs[$i];
967							$gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp;
968						}
969						// IF STILL INSIDE BOX OR STILL VALID
970						// TEST IF circle (perimeter) intersects with
971						// or is enclosed
972						// Point on axis to test
973						$px = $x1 + ($x0 - $x1) * $tmp;
974						$py = $y1 + ($y0 - $y1) * $tmp;
975						$pr = $r * $tmp;
976						$res = $this->testIntersectCircle($px, $py, $pr);
977						if (!$res) {
978							$inside = false;
979						}
980					}
981				}
982			}
983
984			// Gradient STOPs
985			$stops = count($gradient_info['color']);
986			if ($stops < 2) {
987				return '';
988			}
989
990			$range = $gradient_info['color'][count($gradient_info['color']) - 1]['offset'] - $gradient_info['color'][0]['offset'];
991			$min = $gradient_info['color'][0]['offset'];
992
993			for ($i = 0; $i < ($stops); $i++) {
994				if (!$gradient_info['color'][$i]['color']) {
995					if ($gradient_info['colorspace'] == 'RGB') {
996						$gradient_info['color'][$i]['color'] = '0 0 0';
997					} elseif ($gradient_info['colorspace'] == 'Gray') {
998						$gradient_info['color'][$i]['color'] = '0';
999					} elseif ($gradient_info['colorspace'] == 'CMYK') {
1000						$gradient_info['color'][$i]['color'] = '1 1 1 1';
1001					}
1002				}
1003				$offset = ($gradient_info['color'][$i]['offset'] - $min) / $range;
1004				$this->mpdf->gradients[$n]['stops'][] = [
1005					'col' => $gradient_info['color'][$i]['color'],
1006					'opacity' => $gradient_info['color'][$i]['opacity'],
1007					'offset' => $offset];
1008				if ($gradient_info['color'][$i]['opacity'] < 1) {
1009					$trans = true;
1010				}
1011			}
1012			$grx1 = $x1 + ($x0 - $x1) * $gradient_info['color'][0]['offset'];
1013			$gry1 = $y1 + ($y0 - $y1) * $gradient_info['color'][0]['offset'];
1014			$grx2 = $x1 + ($x0 - $x1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset'];
1015			$gry2 = $y1 + ($y0 - $y1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset'];
1016			$grir = $r * $gradient_info['color'][0]['offset'];
1017			$grr = $r * $gradient_info['color'][count($gradient_info['color']) - 1]['offset'];
1018
1019			$this->mpdf->gradients[$n]['coords'] = [$grx1, $gry1, $grx2, $gry2, abs($grr), abs($grir)];
1020
1021			$this->mpdf->gradients[$n]['colorspace'] = $gradient_info['colorspace'];
1022
1023			$this->mpdf->gradients[$n]['type'] = 3;
1024			$this->mpdf->gradients[$n]['fo'] = true;
1025
1026			$this->mpdf->gradients[$n]['extend'] = ['true', 'true'];
1027			if (isset($trans) && $trans) {
1028				$this->mpdf->gradients[$n]['trans'] = true;
1029				$return .= ' /TGS' . ($n) . ' gs ';
1030			}
1031			$return .= ' /Sh' . ($n) . ' sh ';
1032			$return .= " Q\n";
1033		}
1034
1035		return $return;
1036	}
1037
1038	function svgOffset($attribs)
1039	{
1040		// save all <svg> tag attributes
1041		$this->svg_attribs = $attribs;
1042		if (isset($this->svg_attribs['viewBox'])) {
1043			$vb = preg_split('/\s+/is', trim($this->svg_attribs['viewBox']));
1044			if (count($vb) == 4) {
1045				$this->svg_info['x'] = $vb[0];
1046				$this->svg_info['y'] = $vb[1];
1047				$this->svg_info['w'] = $vb[2];
1048				$this->svg_info['h'] = $vb[3];
1049//				return;
1050			}
1051		}
1052		$svg_w = 0;
1053		$svg_h = 0;
1054		if (isset($attribs['width']) && $attribs['width']) {
1055			$svg_w = $this->sizeConverter->convert($attribs['width']); // mm (interprets numbers as pixels)
1056		}
1057		if (isset($attribs['height']) && $attribs['height']) {
1058			$svg_h = $this->sizeConverter->convert($attribs['height']); // mm
1059		}
1060
1061
1062///*
1063		// mPDF 5.0.005
1064		if (isset($this->svg_info['w']) && $this->svg_info['w']) { // if 'w' set by viewBox
1065			if ($svg_w) { // if width also set, use these values to determine to set size of "pixel"
1066				$this->kp *= ($svg_w / 0.2645) / $this->svg_info['w'];
1067				$this->kf = ($svg_w / 0.2645) / $this->svg_info['w'];
1068			} elseif ($svg_h) {
1069				$this->kp *= ($svg_h / 0.2645) / $this->svg_info['h'];
1070				$this->kf = ($svg_h / 0.2645) / $this->svg_info['h'];
1071			}
1072			return;
1073		}
1074//*/
1075		// Added to handle file without height or width specified
1076		if (!$svg_w && !$svg_h) {
1077			$svg_w = $svg_h = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'];
1078		} // DEFAULT
1079		if (!$svg_w) {
1080			$svg_w = $svg_h;
1081		}
1082		if (!$svg_h) {
1083			$svg_h = $svg_w;
1084		}
1085
1086		$this->svg_info['x'] = 0;
1087		$this->svg_info['y'] = 0;
1088		$this->svg_info['w'] = $svg_w / 0.2645; // mm->pixels
1089		$this->svg_info['h'] = $svg_h / 0.2645; // mm->pixels
1090	}
1091
1092	//
1093	// check if points are within svg, if not, set to max
1094	function svg_overflow($x, $y)
1095	{
1096		$x2 = $x;
1097		$y2 = $y;
1098		if (isset($this->svg_attribs['overflow'])) {
1099			if ($this->svg_attribs['overflow'] == 'hidden') {
1100				// Not sure if this is supposed to strip off units, but since I dont use any I will omlt this step
1101				$svg_w = preg_replace("/([0-9\.]*)(.*)/i", "$1", $this->svg_attribs['width']);
1102				$svg_h = preg_replace("/([0-9\.]*)(.*)/i", "$1", $this->svg_attribs['height']);
1103
1104				// $xmax = floor($this->svg_attribs['width']);
1105				$xmax = floor($svg_w);
1106				$xmin = 0;
1107				// $ymax = floor(($this->svg_attribs['height'] * -1));
1108				$ymax = floor(($svg_h * -1));
1109				$ymin = 0;
1110
1111				if ($x > $xmax) {
1112					$x2 = $xmax; // right edge
1113				}
1114				if ($x < $xmin) {
1115					$x2 = $xmin; // left edge
1116				}
1117				if ($y < $ymax) {
1118					$y2 = $ymax; // bottom
1119				}
1120				if ($y > $ymin) {
1121					$y2 = $ymin; // top
1122				}
1123			}
1124		}
1125
1126
1127		return ['x' => $x2, 'y' => $y2];
1128	}
1129
1130	function svgDefineStyle($critere_style)
1131	{
1132
1133		$tmp = count($this->svg_style) - 1;
1134		$current_style = $this->svg_style[$tmp];
1135
1136		unset($current_style['transformations']);
1137
1138		// TRANSFORM SCALE
1139		$transformations = '';
1140		if (isset($critere_style['transform'])) {
1141			preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)\((.*?)\)/is', $critere_style['transform'], $m);
1142			if (count($m[0])) {
1143				for ($i = 0; $i < count($m[0]); $i++) {
1144					$c = strtolower($m[1][$i]);
1145					$v = trim($m[2][$i]);
1146					$vv = preg_split('/[ ,]+/', $v);
1147					if ($c == 'matrix' && count($vv) == 6) {
1148						// mPDF 5.0.039
1149						// Note angle of rotation is reversed (from SVG to PDF), so vv[1] and vv[2] are negated
1150						$transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $vv[0], -$vv[1], -$vv[2], $vv[3], $vv[4] * $this->kp, -$vv[5] * $this->kp);
1151
1152						/*
1153						  // The long way of doing this??
1154						  // need to reverse angle of rotation from SVG to PDF
1155						  $sx=sqrt(pow($vv[0],2)+pow($vv[2],2));
1156						  if ($vv[0] < 0) { $sx *= -1; } // change sign
1157						  $sy=sqrt(pow($vv[1],2)+pow($vv[3],2));
1158						  if ($vv[3] < 0) { $sy *= -1; } // change sign
1159
1160						  // rotation angle is
1161						  $t=atan2($vv[1],$vv[3]);
1162						  $t=atan2(-$vv[2],$vv[0]);	// Should be the same value or skew has been applied
1163
1164						  // Reverse angle
1165						  $t *= -1;
1166
1167						  // Rebuild matrix
1168						  $ma = $sx * cos($t);
1169						  $mb = $sy * sin($t);
1170						  $mc = -$sx * sin($t);
1171						  $md = $sy * cos($t);
1172
1173						  // $transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $ma, $mb, $mc, $md, $vv[4]*$this->kp, -$vv[5]*$this->kp);
1174						 */
1175					} elseif ($c == 'translate' && count($vv)) {
1176						$tm[4] = $vv[0];
1177						if (count($vv) == 2) {
1178							$t_y = -$vv[1];
1179						} else {
1180							$t_y = 0;
1181						}
1182						$tm[5] = $t_y;
1183						$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $tm[4] * $this->kp, $tm[5] * $this->kp);
1184					} elseif ($c == 'scale' && count($vv)) {
1185						if (count($vv) == 2) {
1186							$s_y = $vv[1];
1187						} else {
1188							$s_y = $vv[0];
1189						}
1190						$tm[0] = $vv[0];
1191						$tm[3] = $s_y;
1192						$transformations .= sprintf(' %.3F 0 0 %.3F 0 0 cm ', $tm[0], $tm[3]);
1193					} elseif ($c == 'rotate' && count($vv)) {
1194						$tm[0] = cos(deg2rad(-$vv[0]));
1195						$tm[1] = sin(deg2rad(-$vv[0]));
1196						$tm[2] = -$tm[1];
1197						$tm[3] = $tm[0];
1198						if (count($vv) == 3) {
1199							$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $vv[1] * $this->kp, -$vv[2] * $this->kp);
1200						}
1201						$transformations .= sprintf(' %.3F %.3F %.3F %.3F 0 0 cm ', $tm[0], $tm[1], $tm[2], $tm[3]);
1202						if (count($vv) == 3) {
1203							$transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', -$vv[1] * $this->kp, $vv[2] * $this->kp);
1204						}
1205					} elseif ($c == 'skewx' && count($vv)) {
1206						$tm[2] = tan(deg2rad(-$vv[0]));
1207						$transformations .= sprintf(' 1 0 %.3F 1 0 0 cm ', $tm[2]);
1208					} elseif ($c == 'skewy' && count($vv)) {
1209						$tm[1] = tan(deg2rad(-$vv[0]));
1210						$transformations .= sprintf(' 1 %.3F 0 1 0 0 cm ', $tm[1]);
1211					}
1212				}
1213			}
1214			$current_style['transformations'] = $transformations;
1215		}
1216
1217		if (isset($critere_style['style'])) {
1218			if (preg_match('/fill:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/i', $critere_style['style'], $m)) { // mPDF 5.7.2
1219				$current_style['fill'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT);
1220			} else {
1221				$tmp = preg_replace("/(.*)fill:\s*([a-z0-9#_()]*|none)(.*)/i", "$2", $critere_style['style']);
1222				if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1223					$current_style['fill'] = $tmp;
1224				}
1225			}
1226
1227			// mPDF 5.7.2
1228			if ((preg_match("/[^-]opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m) ||
1229				preg_match("/^opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m)) && $m[1] != 'inherit') {
1230				$current_style['fill-opacity'] = $m[1];
1231				$current_style['stroke-opacity'] = $m[1];
1232			}
1233
1234			$tmp = preg_replace("/(.*)fill-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
1235			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1236				$current_style['fill-opacity'] = $tmp;
1237			}
1238
1239			$tmp = preg_replace("/(.*)fill-rule:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
1240			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1241				$current_style['fill-rule'] = $tmp;
1242			}
1243
1244			if (preg_match('/stroke:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) {
1245				$current_style['stroke'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT);
1246			} else {
1247				$tmp = preg_replace("/(.*)stroke:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
1248				if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1249					$current_style['stroke'] = $tmp;
1250				}
1251			}
1252
1253			$tmp = preg_replace("/(.*)stroke-linecap:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
1254			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1255				$current_style['stroke-linecap'] = $tmp;
1256			}
1257
1258			$tmp = preg_replace("/(.*)stroke-linejoin:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
1259			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1260				$current_style['stroke-linejoin'] = $tmp;
1261			}
1262
1263			$tmp = preg_replace("/(.*)stroke-miterlimit:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
1264			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1265				$current_style['stroke-miterlimit'] = $tmp;
1266			}
1267
1268			$tmp = preg_replace("/(.*)stroke-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
1269			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1270				$current_style['stroke-opacity'] = $tmp;
1271			}
1272
1273			$tmp = preg_replace("/(.*)stroke-width:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
1274			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1275				$current_style['stroke-width'] = $tmp;
1276			}
1277
1278			$tmp = preg_replace("/(.*)stroke-dasharray:\s*([a-z0-9., ]*|none)(.*)/i", "$2", $critere_style['style']);
1279			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1280				$current_style['stroke-dasharray'] = $tmp;
1281			}
1282
1283			$tmp = preg_replace("/(.*)stroke-dashoffset:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
1284			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
1285				$current_style['stroke-dashoffset'] = $tmp;
1286			}
1287		}
1288
1289		// mPDF 5.7.2
1290		if (isset($critere_style['opacity']) && $critere_style['opacity'] != 'inherit') {
1291			$current_style['fill-opacity'] = $critere_style['opacity'];
1292			$current_style['stroke-opacity'] = $critere_style['opacity'];
1293		}
1294
1295		if (isset($critere_style['fill']) && $critere_style['fill'] != 'inherit') {
1296			$current_style['fill'] = $critere_style['fill'];
1297		}
1298
1299		if (isset($critere_style['fill-opacity']) && $critere_style['fill-opacity'] != 'inherit') {
1300			$current_style['fill-opacity'] = $critere_style['fill-opacity'];
1301		}
1302
1303		if (isset($critere_style['fill-rule']) && $critere_style['fill-rule'] != 'inherit') {
1304			$current_style['fill-rule'] = $critere_style['fill-rule'];
1305		}
1306
1307		if (isset($critere_style['stroke']) && $critere_style['stroke'] != 'inherit') {
1308			$current_style['stroke'] = $critere_style['stroke'];
1309		}
1310
1311		if (isset($critere_style['stroke-linecap']) && $critere_style['stroke-linecap'] != 'inherit') {
1312			$current_style['stroke-linecap'] = $critere_style['stroke-linecap'];
1313		}
1314
1315		if (isset($critere_style['stroke-linejoin']) && $critere_style['stroke-linejoin'] != 'inherit') {
1316			$current_style['stroke-linejoin'] = $critere_style['stroke-linejoin'];
1317		}
1318
1319		if (isset($critere_style['stroke-miterlimit']) && $critere_style['stroke-miterlimit'] != 'inherit') {
1320			$current_style['stroke-miterlimit'] = $critere_style['stroke-miterlimit'];
1321		}
1322
1323		if (isset($critere_style['stroke-opacity']) && $critere_style['stroke-opacity'] != 'inherit') {
1324			$current_style['stroke-opacity'] = $critere_style['stroke-opacity'];
1325		}
1326
1327		if (isset($critere_style['stroke-width']) && $critere_style['stroke-width'] != 'inherit') {
1328			$current_style['stroke-width'] = $critere_style['stroke-width'];
1329		}
1330
1331		if (isset($critere_style['stroke-dasharray']) && $critere_style['stroke-dasharray'] != 'inherit') {
1332			$current_style['stroke-dasharray'] = $critere_style['stroke-dasharray'];
1333		}
1334		if (isset($critere_style['stroke-dashoffset']) && $critere_style['stroke-dashoffset'] != 'inherit') {
1335			$current_style['stroke-dashoffset'] = $critere_style['stroke-dashoffset'];
1336		}
1337
1338		// Used as indirect setting for currentColor
1339		if (isset($critere_style['color']) && $critere_style['color'] != 'inherit') {
1340			$current_style['color'] = $critere_style['color'];
1341		}
1342
1343		return $current_style;
1344	}
1345
1346	//
1347	//	Cette fonction ecrit le style dans le stream svg.
1348	function svgStyle($critere_style, $attribs, $element)
1349	{
1350		$path_style = '';
1351		$fill_gradient = '';
1352		$w = '';
1353		$style = '';
1354		if (substr_count($critere_style['fill'], 'url') > 0 && $element != 'line') {
1355			//
1356			// couleur degradé
1357			$id_gradient = preg_replace("/url\(#([\w_]*)\)/i", "$1", $critere_style['fill']);
1358			if ($id_gradient != $critere_style['fill']) {
1359				if (isset($this->svg_gradient[$id_gradient])) {
1360					$fill_gradient = $this->svgGradient($this->svg_gradient[$id_gradient], $attribs, $element);
1361					if ($fill_gradient) {
1362						$path_style = "q ";
1363						$w = "W";
1364						$style .= 'N';
1365					}
1366				}
1367			}
1368		} // Used as indirect setting for currentColor
1369		elseif (strtolower($critere_style['fill']) == 'currentcolor' && $element != 'line') {
1370			$col = $this->colorConverter->convert($critere_style['color'], $this->mpdf->PDFAXwarnings);
1371			if ($col) {
1372				if ($col[0] == 5 && is_numeric($col[4])) {
1373					$critere_style['fill-opacity'] = ord($col[4] / 100);
1374				} // RGBa
1375				if ($col[0] == 6 && is_numeric($col[5])) {
1376					$critere_style['fill-opacity'] = ord($col[5] / 100);
1377				} // CMYKa
1378				$path_style .= $this->mpdf->SetFColor($col, true) . ' ';
1379				$style .= 'F';
1380			}
1381		} elseif ($critere_style['fill'] != 'none' && $element != 'line') {
1382			$col = $this->colorConverter->convert($critere_style['fill'], $this->mpdf->PDFAXwarnings);
1383			if ($col) {
1384				if ($col[0] == 5 && is_numeric($col[4])) {
1385					$critere_style['fill-opacity'] = ord($col[4] / 100);
1386				} // RGBa
1387				if ($col[0] == 6 && is_numeric($col[5])) {
1388					$critere_style['fill-opacity'] = ord($col[5] / 100);
1389				} // CMYKa
1390				$path_style .= $this->mpdf->SetFColor($col, true) . ' ';
1391				$style .= 'F';
1392			}
1393		}
1394		if (substr_count($critere_style['stroke'], 'url') > 0) {
1395			/*
1396			  // Cannot put a gradient on a "stroke" in PDF?
1397			  $id_gradient = preg_replace("/url\(#([\w_]*)\)/i","$1",$critere_style['stroke']);
1398			  if ($id_gradient != $critere_style['stroke']) {
1399			  if (isset($this->svg_gradient[$id_gradient])) {
1400			  $fill_gradient = $this->svgGradient($this->svg_gradient[$id_gradient], $attribs, $element);
1401			  if ($fill_gradient) {
1402			  $path_style = "q ";
1403			  $w = "W";
1404			  $style .= 'D';
1405			  }
1406			  }
1407			  }
1408			 */
1409		} // Used as indirect setting for currentColor
1410		elseif (strtolower($critere_style['stroke']) == 'currentcolor') {
1411			$col = $this->colorConverter->convert($critere_style['color'], $this->mpdf->PDFAXwarnings);
1412			if ($col) {
1413				if ($col[0] == 5 && is_numeric($col[4])) {
1414					$critere_style['stroke-opacity'] = ord($col[4] / 100);
1415				} // RGBa
1416				if ($col[0] == 6 && is_numeric($col[5])) {
1417					$critere_style['stroke-opacity'] = ord($col[5] / 100);
1418				} // CMYKa
1419				$path_style .= $this->mpdf->SetDColor($col, true) . ' ';
1420				$style .= 'D';
1421				$lw = $this->ConvertSVGSizePixels($critere_style['stroke-width']);
1422				$path_style .= sprintf('%.3F w ', $lw * $this->kp);
1423			}
1424		} elseif ($critere_style['stroke'] != 'none') {
1425			$col = $this->colorConverter->convert($critere_style['stroke'], $this->mpdf->PDFAXwarnings);
1426			if ($col) {
1427				// mPDF 5.0.051
1428				// mPDF 5.3.74
1429				if ($col[0] == 5 && is_numeric($col[4])) {
1430					$critere_style['stroke-opacity'] = ord($col[4] / 100);
1431				} // RGBa
1432				if ($col[0] == 6 && is_numeric($col[5])) {
1433					$critere_style['stroke-opacity'] = ord($col[5] / 100);
1434				} // CMYKa
1435				$path_style .= $this->mpdf->SetDColor($col, true) . ' ';
1436				$style .= 'D';
1437				$lw = $this->ConvertSVGSizePixels($critere_style['stroke-width']);
1438				$path_style .= sprintf('%.3F w ', $lw * $this->kp);
1439			}
1440		}
1441
1442
1443		if ($critere_style['stroke'] != 'none') {
1444			if ($critere_style['stroke-linejoin'] == 'miter') {
1445				$path_style .= ' 0 j ';
1446			} elseif ($critere_style['stroke-linejoin'] == 'round') {
1447				$path_style .= ' 1 j ';
1448			} elseif ($critere_style['stroke-linejoin'] == 'bevel') {
1449				$path_style .= ' 2 j ';
1450			}
1451
1452			if ($critere_style['stroke-linecap'] == 'butt') {
1453				$path_style .= ' 0 J ';
1454			} elseif ($critere_style['stroke-linecap'] == 'round') {
1455				$path_style .= ' 1 J ';
1456			} elseif ($critere_style['stroke-linecap'] == 'square') {
1457				$path_style .= ' 2 J ';
1458			}
1459
1460			if (isset($critere_style['stroke-miterlimit'])) {
1461				if ($critere_style['stroke-miterlimit'] == 'none') {
1462				} elseif (preg_match('/^[\d.]+$/', $critere_style['stroke-miterlimit'])) {
1463					$path_style .= sprintf('%.2F M ', $critere_style['stroke-miterlimit']);
1464				}
1465			}
1466
1467			if (isset($critere_style['stroke-dasharray'])) {
1468
1469				$off = 0;
1470				$d = preg_split('/(,\s?|\s)/', $critere_style['stroke-dasharray']);
1471
1472				if (count($d) == 1 && $d[0] == 0) {
1473					$path_style .= '[] 0 d ';
1474				} else {
1475
1476					if (count($d) % 2 == 1) {
1477						$d = array_merge($d, $d);
1478					} // 5, 3, 1 => 5,3,1,5,3,1  OR 3 => 3,3
1479
1480					$arr = '';
1481
1482					for ($i = 0; $i < count($d); $i += 2) {
1483
1484						if ($d[$i] === 'none') {
1485							continue;
1486						}
1487
1488						$arr .= sprintf('%.3F %.3F ', $d[$i] * $this->kp, $d[$i + 1] * $this->kp);
1489					}
1490
1491					if (isset($critere_style['stroke-dashoffset'])) {
1492						$off = $critere_style['stroke-dashoffset'] + 0;
1493					}
1494
1495					$path_style .= sprintf('[%s] %.3F d ', $arr, $off * $this->kp);
1496				}
1497			}
1498		}
1499
1500		if ($critere_style['fill-rule'] == 'evenodd') {
1501			$fr = '*';
1502		} else {
1503			$fr = '';
1504		}
1505
1506		if (isset($critere_style['fill-opacity'])) {
1507			$opacity = 1;
1508			if ($critere_style['fill-opacity'] == 0) {
1509				$opacity = 0;
1510			} elseif ($critere_style['fill-opacity'] > 1) {
1511				$opacity = 1;
1512			} elseif ($critere_style['fill-opacity'] > 0) {
1513				$opacity = $critere_style['fill-opacity'];
1514			} elseif ($critere_style['fill-opacity'] < 0) {
1515				$opacity = 0;
1516			}
1517			$gs = $this->mpdf->AddExtGState(['ca' => $opacity, 'BM' => '/Normal']);
1518			$this->mpdf->extgstates[$gs]['fo'] = true;
1519			$path_style .= sprintf(' /GS%d gs ', $gs);
1520		}
1521
1522		if (isset($critere_style['stroke-opacity'])) {
1523			$opacity = 1;
1524			if ($critere_style['stroke-opacity'] == 0) {
1525				$opacity = 0;
1526			} elseif ($critere_style['stroke-opacity'] > 1) {
1527				$opacity = 1;
1528			} elseif ($critere_style['stroke-opacity'] > 0) {
1529				$opacity = $critere_style['stroke-opacity'];
1530			} elseif ($critere_style['stroke-opacity'] < 0) {
1531				$opacity = 0;
1532			}
1533			$gs = $this->mpdf->AddExtGState(['CA' => $opacity, 'BM' => '/Normal']);
1534			$this->mpdf->extgstates[$gs]['fo'] = true;
1535			$path_style .= sprintf(' /GS%d gs ', $gs);
1536		}
1537
1538		switch ($style) {
1539			case 'F':
1540				$op = 'f';
1541				break;
1542			case 'FD':
1543				$op = 'B';
1544				break;
1545			case 'ND':
1546				$op = 'S';
1547				break;
1548			case 'D':
1549				$op = 'S';
1550				break;
1551			default:
1552				$op = 'n';
1553		}
1554
1555		$prestyle = $path_style . ' ';
1556		$poststyle = $w . ' ' . $op . $fr . ' ' . $fill_gradient . "\n";
1557		return [$prestyle, $poststyle];
1558	}
1559
1560	//	fonction retracant les <path />
1561	function svgPath($command, $arguments)
1562	{
1563		$path_cmd = '';
1564		$newsubpath = false;
1565		// mPDF 5.0.039
1566		$minl = $this->pathBBox[0];
1567		$mint = $this->pathBBox[1];
1568		$maxr = $this->pathBBox[2] + $this->pathBBox[0];
1569		$maxb = $this->pathBBox[3] + $this->pathBBox[1];
1570
1571		$start = [$this->xbase, -$this->ybase];
1572
1573		// taken from https://github.com/PhenX/php-svg-lib/blob/master/src/Svg/Tag/Path.php#L47
1574		// Handle args like: a5.38022,5.38022,0,0,1-2.4207.72246,4.50524,4.50524,0,0,1-3.12681-1.33942,9.67442,9.67442,0,0,1-2.38273-3.016,1.87506,1.87506,0,0,1,.34979-2.43562
1575		preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $arguments, $a, PREG_PATTERN_ORDER);
1576		$a = $a[0];
1577
1578		//	if the command is a capital letter, the coords go absolute, otherwise relative
1579		if (strtolower($command) == $command) {
1580			$relative = true;
1581		} else {
1582			$relative = false;
1583		}
1584
1585
1586		$argumentCount = count($a);
1587
1588		//	each command may have different needs for arguments [1 to 8]
1589
1590		switch (strtolower($command)) {
1591
1592			case 'm': // move
1593
1594				for ($i = 0; $i < $argumentCount; $i += 2) {
1595
1596					$x = $a[$i];
1597					$y = $a[$i + 1];
1598
1599					if ($relative) {
1600
1601						$pdfx = ($this->xbase + $x);
1602						$pdfy = ($this->ybase - $y);
1603						$this->xbase += $x;
1604						$this->ybase += -$y;
1605
1606					} else {
1607
1608						$pdfx = $x;
1609						$pdfy = -$y;
1610						$this->xbase = $x;
1611						$this->ybase = -$y;
1612					}
1613
1614					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1615
1616					$minl = min($minl, $pdf_pt['x']);
1617					$maxr = max($maxr, $pdf_pt['x']);
1618					$mint = min($mint, -$pdf_pt['y']);
1619					$maxb = max($maxb, -$pdf_pt['y']);
1620
1621					if ($i == 0) {
1622						$path_cmd .= sprintf('%.3F %.3F m ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1623					} else {
1624						$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1625					}
1626
1627					// mPDF 4.4.003  Save start points of subpath
1628					if ($this->subPathInit) {
1629						$this->spxstart = $this->xbase;
1630						$this->spystart = $this->ybase;
1631						$this->subPathInit = false;
1632					}
1633				}
1634
1635				break;
1636
1637			case 'l': // a simple line
1638
1639				for ($i = 0; $i < $argumentCount; $i+=2) {
1640
1641					$x = ($a[$i]);
1642					$y = ($a[$i + 1]);
1643
1644					if ($relative) {
1645
1646						$pdfx = ($this->xbase + $x);
1647						$pdfy = ($this->ybase - $y);
1648						$this->xbase += $x;
1649						$this->ybase += -$y;
1650
1651					} else {
1652
1653						$pdfx = $x;
1654						$pdfy = -$y;
1655						$this->xbase = $x;
1656						$this->ybase = -$y;
1657
1658					}
1659
1660					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1661
1662					$minl = min($minl, $pdf_pt['x']);
1663					$maxr = max($maxr, $pdf_pt['x']);
1664					$mint = min($mint, -$pdf_pt['y']);
1665					$maxb = max($maxb, -$pdf_pt['y']);
1666
1667					$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1668				}
1669
1670				break;
1671
1672			case 'h': // a very simple horizontal line
1673
1674				for ($i = 0; $i < $argumentCount; $i++) {
1675
1676					$x = ($a[$i]);
1677
1678					if ($relative) {
1679
1680						$y = 0;
1681						$pdfx = ($this->xbase + $x);
1682						$pdfy = ($this->ybase - $y);
1683						$this->xbase += $x;
1684						$this->ybase += -$y;
1685
1686					} else {
1687
1688						$y = -$this->ybase;
1689						$pdfx = $x;
1690						$pdfy = -$y;
1691						$this->xbase = $x;
1692						$this->ybase = -$y;
1693
1694					}
1695
1696					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1697					$minl = min($minl, $pdf_pt['x']);
1698					$maxr = max($maxr, $pdf_pt['x']);
1699					$mint = min($mint, -$pdf_pt['y']);
1700					$maxb = max($maxb, -$pdf_pt['y']);
1701					$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1702				}
1703
1704				break;
1705
1706			case 'v': // the simplest line, vertical
1707
1708				for ($i = 0; $i < $argumentCount; $i++) {
1709
1710					$y = ($a[$i]);
1711
1712					if ($relative) {
1713
1714						$x = 0;
1715						$pdfx = ($this->xbase + $x);
1716						$pdfy = ($this->ybase - $y);
1717						$this->xbase += $x;
1718						$this->ybase += -$y;
1719
1720					} else {
1721
1722						$x = $this->xbase;
1723						$pdfx = $x;
1724						$pdfy = -$y;
1725						$this->xbase = $x;
1726						$this->ybase = -$y;
1727
1728					}
1729
1730					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1731					$minl = min($minl, $pdf_pt['x']);
1732					$maxr = max($maxr, $pdf_pt['x']);
1733					$mint = min($mint, -$pdf_pt['y']);
1734					$maxb = max($maxb, -$pdf_pt['y']);
1735					$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1736
1737				}
1738
1739				break;
1740
1741			case 's': // bezier with first vertex equal first control
1742
1743				// mPDF 4.4.003
1744
1745				if (!($this->lastcommand == 'C' || $this->lastcommand == 'c' || $this->lastcommand == 'S' || $this->lastcommand == 's')) {
1746					$this->lastcontrolpoints = [0, 0];
1747				}
1748
1749				for ($i = 0; $i < $argumentCount; $i += 4) {
1750
1751					$x1 = $this->lastcontrolpoints[0];
1752					$y1 = $this->lastcontrolpoints[1];
1753
1754					$x2 = ($a[$i]);
1755					$y2 = ($a[$i + 1]);
1756
1757					$x = ($a[$i + 2]);
1758					$y = ($a[$i + 3]);
1759
1760					if ($relative) {
1761
1762						$pdfx1 = ($this->xbase + $x1);
1763						$pdfy1 = ($this->ybase - $y1);
1764						$pdfx2 = ($this->xbase + $x2);
1765						$pdfy2 = ($this->ybase - $y2);
1766						$pdfx = ($this->xbase + $x);
1767						$pdfy = ($this->ybase - $y);
1768						$this->xbase += $x;
1769						$this->ybase += -$y;
1770
1771					} else {
1772
1773						$pdfx1 = $this->xbase + $x1;
1774						$pdfy1 = $this->ybase - $y1;
1775						$pdfx2 = $x2;
1776						$pdfy2 = -$y2;
1777						$pdfx = $x;
1778						$pdfy = -$y;
1779						$this->xbase = $x;
1780						$this->ybase = -$y;
1781
1782					}
1783
1784					$this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative
1785
1786					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1787
1788					$curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy];
1789					$bx = $this->computeBezierBoundingBox($start, $curves);
1790					$minl = min($minl, $bx[0]);
1791					$maxr = max($maxr, $bx[2]);
1792					$mint = min($mint, $bx[1]);
1793					$maxb = max($maxb, $bx[3]);
1794
1795					if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) {
1796						$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1797					} else {
1798						$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp);
1799					}
1800				}
1801
1802				break;
1803
1804			case 'c': // bezier with second vertex equal second control
1805
1806				for ($i = 0; $i < $argumentCount; $i += 6) {
1807
1808					$x1 = ($a[$i]);
1809					$y1 = ($a[$i + 1]);
1810					$x2 = ($a[$i + 2]);
1811					$y2 = ($a[$i + 3]);
1812					$x = ($a[$i + 4]);
1813					$y = ($a[$i + 5]);
1814
1815					if ($relative) {
1816
1817						$pdfx1 = ($this->xbase + $x1);
1818						$pdfy1 = ($this->ybase - $y1);
1819						$pdfx2 = ($this->xbase + $x2);
1820						$pdfy2 = ($this->ybase - $y2);
1821						$pdfx = ($this->xbase + $x);
1822						$pdfy = ($this->ybase - $y);
1823						$this->xbase += $x;
1824						$this->ybase += -$y;
1825
1826					} else {
1827
1828						$pdfx1 = $x1;
1829						$pdfy1 = -$y1;
1830						$pdfx2 = $x2;
1831						$pdfy2 = -$y2;
1832						$pdfx = $x;
1833						$pdfy = -$y;
1834						$this->xbase = $x;
1835						$this->ybase = -$y;
1836
1837					}
1838
1839					$this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative
1840					// $pdf_pt2 = $this->svg_overflow($pdfx2,$pdfy2);
1841					// $pdf_pt1 = $this->svg_overflow($pdfx1,$pdfy1);
1842					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1843
1844					$curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy];
1845					$bx = $this->computeBezierBoundingBox($start, $curves);
1846					$minl = min($minl, $bx[0]);
1847					$maxr = max($maxr, $bx[2]);
1848					$mint = min($mint, $bx[1]);
1849					$maxb = max($maxb, $bx[3]);
1850
1851					if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) {
1852						$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1853					} else {
1854						$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp);
1855					}
1856				}
1857
1858				break;
1859
1860			case 'q': // bezier quadratic avec point de control
1861
1862				for ($i = 0; $i < $argumentCount; $i += 4) {
1863
1864					$x1 = ($a[$i]);
1865					$y1 = ($a[$i + 1]);
1866					$x = ($a[$i + 2]);
1867					$y = ($a[$i + 3]);
1868
1869					if ($relative) {
1870
1871						$pdfx = ($this->xbase + $x);
1872						$pdfy = ($this->ybase - $y);
1873
1874						$pdfx1 = ($this->xbase + ($x1 * 2 / 3));
1875						$pdfy1 = ($this->ybase - ($y1 * 2 / 3));
1876						// mPDF 4.4.003
1877						$pdfx2 = $pdfx1 + 1 / 3 * ($x);
1878						$pdfy2 = $pdfy1 + 1 / 3 * (-$y);
1879
1880						$this->xbase += $x;
1881						$this->ybase += -$y;
1882
1883					} else {
1884
1885						$pdfx = $x;
1886						$pdfy = -$y;
1887
1888						$pdfx1 = ($this->xbase + (($x1 - $this->xbase) * 2 / 3));
1889						$pdfy1 = ($this->ybase - (($y1 + $this->ybase) * 2 / 3));
1890
1891						$pdfx2 = ($x + (($x1 - $x) * 2 / 3));
1892						$pdfy2 = (-$y - (($y1 - $y) * 2 / 3));
1893
1894						// mPDF 4.4.003
1895						$pdfx2 = $pdfx1 + 1 / 3 * ($x - $this->xbase);
1896						$pdfy2 = $pdfy1 + 1 / 3 * (-$y - $this->ybase);
1897
1898						$this->xbase = $x;
1899						$this->ybase = -$y;
1900
1901					}
1902
1903					$this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative
1904
1905					$pdf_pt = $this->svg_overflow($pdfx, $pdfy);
1906
1907					$curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy];
1908					$bx = $this->computeBezierBoundingBox($start, $curves);
1909					$minl = min($minl, $bx[0]);
1910					$maxr = max($maxr, $bx[2]);
1911					$mint = min($mint, $bx[1]);
1912					$maxb = max($maxb, $bx[3]);
1913
1914					if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) {
1915						$path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp);
1916					} else {
1917						$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp);
1918					}
1919				}
1920
1921				break;
1922
1923			case 't': // bezier quadratic avec point de control simetrique a lancien point de control
1924
1925				if (!($this->lastcommand == 'Q' || $this->lastcommand == 'q' || $this->lastcommand == 'T' || $this->lastcommand == 't')) {
1926					$this->lastcontrolpoints = [0, 0];
1927				}
1928
1929				for ($i = 0; $i < $argumentCount; $i += 2) {
1930
1931					$x = ($a[$i]);
1932					$y = ($a[$i + 1]);
1933
1934					$x1 = $this->lastcontrolpoints[0];
1935					$y1 = $this->lastcontrolpoints[1];
1936
1937					if ($relative) {
1938						$pdfx = ($this->xbase + $x);
1939						$pdfy = ($this->ybase - $y);
1940
1941						$pdfx1 = ($this->xbase + ($x1));
1942						$pdfy1 = ($this->ybase - ($y1));
1943						// mPDF 4.4.003
1944						$pdfx2 = $pdfx1 + 1 / 3 * ($x);
1945						$pdfy2 = $pdfy1 + 1 / 3 * (-$y);
1946
1947						$this->xbase += $x;
1948						$this->ybase += -$y;
1949
1950					} else {
1951
1952						$pdfx = $x;
1953						$pdfy = -$y;
1954
1955						$pdfx1 = ($this->xbase + ($x1));
1956						$pdfy1 = ($this->ybase - ($y1));
1957
1958						// mPDF 4.4.003
1959						$pdfx2 = $pdfx1 + 1 / 3 * ($x - $this->xbase);
1960						$pdfy2 = $pdfy1 + 1 / 3 * (-$y - $this->ybase);
1961
1962						$this->xbase = $x;
1963						$this->ybase = -$y;
1964					}
1965
1966					$this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative
1967
1968					$curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy];
1969					$bx = $this->computeBezierBoundingBox($start, $curves);
1970					$minl = min($minl, $bx[0]);
1971					$maxr = max($maxr, $bx[2]);
1972					$mint = min($mint, $bx[1]);
1973					$maxb = max($maxb, $bx[3]);
1974
1975					$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp);
1976				}
1977
1978				break;
1979
1980			case 'a': // Elliptical arc
1981
1982				for ($i = 0; $i < $argumentCount; $i += 7) {
1983
1984					$rx = isset($a[$i]) ? $a[$i] : 0;
1985					$ry = isset($a[$i + 1]) ? $a[$i + 1] : 0;
1986
1987					// x-axis-rotation
1988					$angle = isset($a[$i + 2]) ? $a[$i + 2] : 0;
1989
1990					$largeArcFlag = isset($a[$i + 3]) ? $a[$i + 3] : 0;
1991					$sweepFlag = isset($a[$i + 4]) ? $a[$i + 4] : 0;
1992
1993					$x2 = isset($a[$i + 5]) ? $a[$i + 5] : 0;
1994					$y2 = isset($a[$i + 6]) ? $a[$i + 6] : 0;
1995
1996					$x1 = $this->xbase;
1997					$y1 = -$this->ybase;
1998
1999					if ($relative) {
2000						$x2 = $this->xbase + $x2;
2001						$y2 = -$this->ybase + $y2;
2002						$this->xbase += isset($a[$i + 5]) ? $a[$i + 5] : 0;
2003						$this->ybase += isset($a[$i + 6]) ? -$a[$i + 6] : 0;
2004					} else {
2005						$this->xbase = $x2;
2006						$this->ybase = -$y2;
2007					}
2008
2009					list($pcmd, $bounds) = $this->Arcto($x1, $y1, $x2, $y2, $rx, $ry, $angle, $largeArcFlag, $sweepFlag);
2010
2011					$minl = min($minl, $x2, min($bounds[0]));
2012					$maxr = max($maxr, $x2, max($bounds[0]));
2013					$mint = min($mint, $y2, min($bounds[1]));
2014					$maxb = max($maxb, $y2, max($bounds[1]));
2015
2016					$path_cmd .= $pcmd;
2017
2018				}
2019
2020				break;
2021
2022			case 'z':
2023
2024				$path_cmd .= 'h ';
2025
2026				$this->subPathInit = true;
2027				$newsubpath = true;
2028				$this->xbase = $this->spxstart;
2029				$this->ybase = $this->spystart;
2030
2031				break;
2032		}
2033
2034		if (!$newsubpath) {
2035			$this->subPathInit = false;
2036		}
2037
2038		$this->lastcommand = $command;
2039
2040		// mPDF 5.0.039
2041		$this->pathBBox[0] = $minl;
2042		$this->pathBBox[1] = $mint;
2043		$this->pathBBox[2] = $maxr - $this->pathBBox[0];
2044		$this->pathBBox[3] = $maxb - $this->pathBBox[1];
2045
2046		return $path_cmd;
2047	}
2048
2049	function Arcto($x1, $y1, $x2, $y2, $rx, $ry, $angle, $largeArcFlag, $sweepFlag)
2050	{
2051
2052		$bounds = [0 => [$x1, $x2], 1 => [$y1, $y2]];
2053
2054		// 1. Treat out-of-range parameters as described in
2055		// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
2056		// If the endpoints (x1, y1) and (x2, y2) are identical, then this
2057		// is equivalent to omitting the elliptical arc segment entirely
2058		if ($x1 == $x2 && $y1 == $y2) {
2059			return ['', $bounds]; // mPD 5.0.040
2060		}
2061
2062		// If rX = 0 or rY = 0 then this arc is treated as a straight line
2063		// segment (a "lineto") joining the endpoints.
2064		if ($rx == 0.0 || $ry == 0.0) {
2065			//   return array(Lineto(x2, y2), $bounds);
2066		}
2067
2068		// If rX or rY have negative signs, these are dropped; the absolute
2069		// value is used instead.
2070		if ($rx < 0.0) {
2071			$rx = -$rx;
2072		}
2073
2074		if ($ry < 0.0) {
2075			$ry = -$ry;
2076		}
2077
2078		// 2. convert to center parameterization as shown in
2079		// http://www.w3.org/TR/SVG/implnote.html
2080		$sinPhi = sin(deg2rad($angle));
2081		$cosPhi = cos(deg2rad($angle));
2082
2083		$x1dash = $cosPhi * ($x1 - $x2) / 2.0 + $sinPhi * ($y1 - $y2) / 2.0;
2084		$y1dash = -$sinPhi * ($x1 - $x2) / 2.0 + $cosPhi * ($y1 - $y2) / 2.0;
2085
2086		$numerator = $rx * $rx * $ry * $ry - $rx * $rx * $y1dash * $y1dash - $ry * $ry * $x1dash * $x1dash;
2087
2088		if ($numerator < 0.0) {
2089
2090			//  If rX , rY and are such that there is no solution (basically,
2091			//  the ellipse is not big enough to reach from (x1, y1) to (x2,
2092			//  y2)) then the ellipse is scaled up uniformly until there is
2093			//  exactly one solution (until the ellipse is just big enough).
2094			// -> find factor s, such that numerator' with rx'=s*rx and
2095			//    ry'=s*ry becomes 0 :
2096			$s = sqrt(1.0 - $numerator / ($rx * $rx * $ry * $ry));
2097
2098			$rx *= $s;
2099			$ry *= $s;
2100			$root = 0.0;
2101
2102		} else {
2103			$root = ($largeArcFlag == $sweepFlag ? -1.0 : 1.0) * sqrt($numerator / ($rx * $rx * $y1dash * $y1dash + $ry * $ry * $x1dash * $x1dash));
2104		}
2105
2106		$cxdash = $root * $rx * $y1dash / $ry;
2107		$cydash = -$root * $ry * $x1dash / $rx;
2108
2109		$cx = $cosPhi * $cxdash - $sinPhi * $cydash + ($x1 + $x2) / 2.0;
2110		$cy = $sinPhi * $cxdash + $cosPhi * $cydash + ($y1 + $y2) / 2.0;
2111
2112
2113		$theta1 = $this->CalcVectorAngle(1.0, 0.0, ($x1dash - $cxdash) / $rx, ($y1dash - $cydash) / $ry);
2114		$dtheta = $this->CalcVectorAngle(($x1dash - $cxdash) / $rx, ($y1dash - $cydash) / $ry, (-$x1dash - $cxdash) / $rx, (-$y1dash - $cydash) / $ry);
2115
2116		if (!$sweepFlag && $dtheta > 0) {
2117			$dtheta -= 2.0 * M_PI;
2118		} elseif ($sweepFlag && $dtheta < 0) {
2119			$dtheta += 2.0 * M_PI;
2120		}
2121
2122		// 3. convert into cubic bezier segments <= 90deg
2123		$segments = ceil(abs($dtheta / (M_PI / 2.0)));
2124		$delta = $dtheta / $segments;
2125		$t = 8.0 / 3.0 * sin($delta / 4.0) * sin($delta / 4.0) / sin($delta / 2.0);
2126		$coords = [];
2127
2128		for ($i = 0; $i < $segments; $i++) {
2129
2130			$cosTheta1 = cos($theta1);
2131			$sinTheta1 = sin($theta1);
2132
2133			$theta2 = $theta1 + $delta;
2134
2135			$cosTheta2 = cos($theta2);
2136			$sinTheta2 = sin($theta2);
2137
2138			// a) calculate endpoint of the segment:
2139			$xe = $cosPhi * $rx * $cosTheta2 - $sinPhi * $ry * $sinTheta2 + $cx;
2140			$ye = $sinPhi * $rx * $cosTheta2 + $cosPhi * $ry * $sinTheta2 + $cy;
2141
2142			// b) calculate gradients at start/end points of segment:
2143			$dx1 = $t * ( - $cosPhi * $rx * $sinTheta1 - $sinPhi * $ry * $cosTheta1);
2144			$dy1 = $t * ( - $sinPhi * $rx * $sinTheta1 + $cosPhi * $ry * $cosTheta1);
2145
2146			$dxe = $t * ( $cosPhi * $rx * $sinTheta2 + $sinPhi * $ry * $cosTheta2);
2147			$dye = $t * ( $sinPhi * $rx * $sinTheta2 - $cosPhi * $ry * $cosTheta2);
2148
2149			// c) draw the cubic bezier:
2150			$coords[$i] = [($x1 + $dx1), ($y1 + $dy1), ($xe + $dxe), ($ye + $dye), $xe, $ye];
2151
2152			// do next segment
2153			$theta1 = $theta2;
2154			$x1 = $xe;
2155			$y1 = $ye;
2156		}
2157
2158		$path = ' ';
2159
2160		foreach ($coords as $c) {
2161
2162			$cpx1 = $c[0];
2163			$cpy1 = $c[1];
2164			$cpx2 = $c[2];
2165			$cpy2 = $c[3];
2166			$x2 = $c[4];
2167			$y2 = $c[5];
2168			$path .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $cpx1 * $this->kp, -$cpy1 * $this->kp, $cpx2 * $this->kp, -$cpy2 * $this->kp, $x2 * $this->kp, -$y2 * $this->kp) . "\n";
2169
2170			// mPDF 5.0.040
2171			$bounds[0][] = $c[4];
2172			$bounds[1][] = $c[5];
2173		}
2174
2175		return [$path, $bounds]; // mPDF 5.0.040
2176	}
2177
2178	function CalcVectorAngle($ux, $uy, $vx, $vy)
2179	{
2180		$ta = atan2($uy, $ux);
2181		$tb = atan2($vy, $vx);
2182
2183		if ($tb >= $ta) {
2184			return ($tb - $ta);
2185		}
2186
2187		return (6.28318530718 - ($ta - $tb));
2188	}
2189
2190	function ConvertSVGSizePixels($size = 5, $maxsize = 'x')
2191	{
2192		// maxsize in pixels (user units) or 'y' or 'x'
2193		// e.g. $w = $this->ConvertSVGSizePixels($arguments['w'],$this->svg_info['w']*(25.4/$this->mpdf->dpi));
2194		// usefontsize - setfalse for e.g. margins - will ignore fontsize for % values
2195		// Depends of maxsize value to make % work properly. Usually maxsize == pagewidth
2196		// For text $maxsize = Fontsize
2197		// Setting e.g. margin % will use maxsize (pagewidth) and em will use fontsize
2198
2199		if ($maxsize == 'y') {
2200			$maxsize = $this->svg_info['h'];
2201		} elseif ($maxsize == 'x') {
2202			$maxsize = $this->svg_info['w'];
2203		}
2204
2205		$maxsize *= (25.4 / $this->mpdf->dpi); // convert pixels to mm
2206		$fontsize = $this->mpdf->FontSize / $this->kf;
2207
2208		// Return as pixels
2209		$size = $this->sizeConverter->convert($size, $maxsize, $fontsize, false) * 1 / (25.4 / $this->mpdf->dpi);
2210
2211		return $size;
2212	}
2213
2214	function ConvertSVGSizePts($size = 5)
2215	{
2216		// usefontsize - setfalse for e.g. margins - will ignore fontsize for % values
2217		// Depends of maxsize value to make % work properly. Usually maxsize == pagewidth
2218		// For text $maxsize = Fontsize
2219		// Setting e.g. margin % will use maxsize (pagewidth) and em will use fontsize
2220		$maxsize = $this->mpdf->FontSize;
2221
2222		// Return as pts
2223		$size = $this->sizeConverter->convert($size, $maxsize, false, true) * 72 / 25.4;
2224
2225		return $size;
2226	}
2227
2228	function svgRect($arguments)
2229	{
2230		if ($arguments['h'] == 0 || $arguments['w'] == 0) {
2231			return '';
2232		}
2233
2234		$x = $this->ConvertSVGSizePixels($arguments['x'], 'x'); // mPDF 4.4.003
2235		$y = $this->ConvertSVGSizePixels($arguments['y'], 'y'); // mPDF 4.4.003
2236		$h = $this->ConvertSVGSizePixels($arguments['h'], 'y'); // mPDF 4.4.003
2237		$w = $this->ConvertSVGSizePixels($arguments['w'], 'x'); // mPDF 4.4.003
2238		$rx = $this->ConvertSVGSizePixels($arguments['rx'], 'x'); // mPDF 4.4.003
2239		$ry = $this->ConvertSVGSizePixels($arguments['ry'], 'y'); // mPDF 4.4.003
2240
2241		// mPDF 4.4.003
2242		if ($rx > $w / 2) {
2243			$rx = $w / 2;
2244		}
2245
2246		// mPDF 4.4.003
2247		if ($ry > $h / 2) {
2248			$ry = $h / 2;
2249		}
2250
2251		if ($rx > 0 and $ry == 0) {
2252			$ry = $rx;
2253		}
2254
2255		if ($ry > 0 and $rx == 0) {
2256			$rx = $ry;
2257		}
2258
2259		if ($rx == 0 and $ry == 0) {
2260			//	trace un rectangle sans angle arrondit
2261			$path_cmd = sprintf('%.3F %.3F m ', ($x * $this->kp), -($y * $this->kp));
2262			$path_cmd .= sprintf('%.3F %.3F l ', (($x + $w) * $this->kp), -($y * $this->kp));
2263			$path_cmd .= sprintf('%.3F %.3F l ', (($x + $w) * $this->kp), -(($y + $h) * $this->kp));
2264			$path_cmd .= sprintf('%.3F %.3F l ', ($x) * $this->kp, -(($y + $h) * $this->kp));
2265			$path_cmd .= sprintf('%.3F %.3F l h ', ($x * $this->kp), -($y * $this->kp));
2266		} else {
2267			//	trace un rectangle avec les arrondit
2268			//	les points de controle du bezier sont deduis grace a la constante kappa
2269			$kappa = 4 * (sqrt(2) - 1) / 3;
2270
2271			$kx = $kappa * $rx;
2272			$ky = $kappa * $ry;
2273
2274			$path_cmd = sprintf('%.3F %.3F m ', ($x + $rx) * $this->kp, -$y * $this->kp);
2275			$path_cmd .= sprintf('%.3F %.3F l ', ($x + ($w - $rx)) * $this->kp, -$y * $this->kp);
2276			$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + ($w - $rx + $kx)) * $this->kp, -$y * $this->kp, ($x + $w) * $this->kp, (-$y + (-$ry + $ky)) * $this->kp, ($x + $w) * $this->kp, (-$y + (-$ry)) * $this->kp);
2277			$path_cmd .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-$y + (-$h + $ry)) * $this->kp);
2278			$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + $w) * $this->kp, (-$y + (-$h - $ky + $ry)) * $this->kp, ($x + ($w - $rx + $kx)) * $this->kp, (-$y + (-$h)) * $this->kp, ($x + ($w - $rx)) * $this->kp, (-$y + (-$h)) * $this->kp);
2279
2280			$path_cmd .= sprintf('%.3F %.3F l ', ($x + $rx) * $this->kp, (-$y + (-$h)) * $this->kp);
2281			$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + ($rx - $kx)) * $this->kp, (-$y + (-$h)) * $this->kp, $x * $this->kp, (-$y + (-$h - $ky + $ry)) * $this->kp, $x * $this->kp, (-$y + (-$h + $ry)) * $this->kp);
2282			$path_cmd .= sprintf('%.3F %.3F l ', $x * $this->kp, (-$y + (-$ry)) * $this->kp);
2283			$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c h ', $x * $this->kp, (-$y + (-$ry + $ky)) * $this->kp, ($x + ($rx - $kx)) * $this->kp, -$y * $this->kp, ($x + $rx) * $this->kp, -$y * $this->kp);
2284		}
2285		return $path_cmd;
2286	}
2287
2288	/**
2289	 * fonction retracant les <ellipse /> et <circle />
2290	 * le cercle est tracé grave a 4 bezier cubic, les poitn de controles
2291	 * sont deduis grace a la constante kappa * rayon
2292	 */
2293	function svgEllipse($arguments)
2294	{
2295		if ($arguments['rx'] == 0 || $arguments['ry'] == 0) {
2296			return '';
2297		}
2298
2299		$kappa = 4 * (sqrt(2) - 1) / 3;
2300
2301		$cx = $this->ConvertSVGSizePixels($arguments['cx'], 'x');
2302		$cy = $this->ConvertSVGSizePixels($arguments['cy'], 'y');
2303		$rx = $this->ConvertSVGSizePixels($arguments['rx'], 'x');
2304		$ry = $this->ConvertSVGSizePixels($arguments['ry'], 'y');
2305
2306		$x1 = $cx;
2307		$y1 = -$cy + $ry;
2308
2309		$x2 = $cx + $rx;
2310		$y2 = -$cy;
2311
2312		$x3 = $cx;
2313		$y3 = -$cy - $ry;
2314
2315		$x4 = $cx - $rx;
2316		$y4 = -$cy;
2317
2318		$path_cmd = sprintf('%.3F %.3F m ', $x1 * $this->kp, $y1 * $this->kp);
2319		$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x1 + ($rx * $kappa)) * $this->kp, $y1 * $this->kp, $x2 * $this->kp, ($y2 + ($ry * $kappa)) * $this->kp, $x2 * $this->kp, $y2 * $this->kp);
2320		$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $x2 * $this->kp, ($y2 - ($ry * $kappa)) * $this->kp, ($x3 + ($rx * $kappa)) * $this->kp, $y3 * $this->kp, $x3 * $this->kp, $y3 * $this->kp);
2321		$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x3 - ($rx * $kappa)) * $this->kp, $y3 * $this->kp, $x4 * $this->kp, ($y4 - ($ry * $kappa)) * $this->kp, $x4 * $this->kp, $y4 * $this->kp);
2322		$path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $x4 * $this->kp, ($y4 + ($ry * $kappa)) * $this->kp, ($x1 - ($rx * $kappa)) * $this->kp, $y1 * $this->kp, $x1 * $this->kp, $y1 * $this->kp);
2323		$path_cmd .= 'h ';
2324
2325		return $path_cmd;
2326	}
2327
2328	function svgPolyline($arguments, $ispolyline = true)
2329	{
2330		if ($ispolyline) {
2331			$xbase = $arguments[0];
2332			$ybase = - $arguments[1];
2333		} else {
2334			if ($arguments[0] == $arguments[2] && $arguments[1] == $arguments[3]) {
2335				return '';
2336			} // Zero length line
2337			$xbase = $this->ConvertSVGSizePixels($arguments[0], 'x');
2338			$ybase = - $this->ConvertSVGSizePixels($arguments[1], 'y');
2339		}
2340
2341		$path_cmd = sprintf('%.3F %.3F m ', $xbase * $this->kp, $ybase * $this->kp);
2342
2343		for ($i = 2; $i < count($arguments); $i += 2) {
2344			if ($ispolyline) {
2345				$tmp_x = $arguments[$i];
2346				$tmp_y = - $arguments[($i + 1)];
2347			} else {
2348				$tmp_x = $this->ConvertSVGSizePixels($arguments[$i], 'x');
2349				$tmp_y = - $this->ConvertSVGSizePixels($arguments[($i + 1)], 'y');
2350			}
2351			$path_cmd .= sprintf('%.3F %.3F l ', $tmp_x * $this->kp, $tmp_y * $this->kp);
2352		}
2353
2354		//	$path_cmd .= 'h '; // ?? In error - don't close subpath here
2355		return $path_cmd;
2356	}
2357
2358	function svgPolygon($arguments)
2359	{
2360		$xbase = $arguments[0];
2361		$ybase = - $arguments[1];
2362		$path_cmd = sprintf('%.3F %.3F m ', $xbase * $this->kp, $ybase * $this->kp);
2363
2364		for ($i = 2; $i < count($arguments); $i += 2) {
2365			$tmp_x = $arguments[$i];
2366			$tmp_y = - $arguments[($i + 1)];
2367
2368			$path_cmd .= sprintf('%.3F %.3F l ', $tmp_x * $this->kp, $tmp_y * $this->kp);
2369		}
2370
2371		$path_cmd .= sprintf('%.3F %.3F l ', $xbase * $this->kp, $ybase * $this->kp);
2372		$path_cmd .= 'h ';
2373
2374		return $path_cmd;
2375	}
2376
2377	function svgText()
2378	{
2379		$current_style = $this->txt_style[count($this->txt_style) - 1]; // mPDF 5.7.4
2380		$style = '';
2381		$op = '';
2382		$render = -1;
2383
2384		if (isset($this->txt_data[2])) {
2385
2386			// mPDF 6
2387			// If using SVG Font
2388			if (isset($this->svg_font[$current_style['font-family']])) {
2389
2390				// select font
2391				$style = 'R';
2392				$style .= (isset($current_style['font-weight']) && $current_style['font-weight'] == 'bold') ? 'B' : '';
2393				$style .= (isset($current_style['font-style']) && $current_style['font-style'] == 'italic') ? 'I' : '';
2394				$style .= (isset($current_style['font-variant']) && $current_style['font-variant'] == 'small-caps') ? 'S' : '';
2395
2396				$fontsize = $current_style['font-size'] * $this->mpdf->dpi / 72;
2397
2398				if (isset($this->svg_font[$current_style['font-family']][$style])) {
2399					$svg_font = $this->svg_font[$current_style['font-family']][$style];
2400				} elseif (isset($this->svg_font[$current_style['font-family']]['R'])) {
2401					$svg_font = $this->svg_font[$current_style['font-family']]['R'];
2402				}
2403
2404				if (!isset($svg_font['units-per-em']) || $svg_font['units-per-em'] < 1) {
2405					$svg_font['units-per-em'] = 1000;
2406				}
2407
2408				$units_per_em = $svg_font['units-per-em'];
2409				$scale = $fontsize / $units_per_em;
2410				$stroke_width = $current_style['stroke-width'];
2411				$stroke_width /= $scale;
2412
2413				$opacitystr = '';
2414				$fopacity = 1;
2415
2416				if (isset($current_style['fill-opacity'])) {
2417					if ($current_style['fill-opacity'] == 0) {
2418						$fopacity = 0;
2419					} elseif ($current_style['fill-opacity'] > 1) {
2420						$fopacity = 1;
2421					} elseif ($current_style['fill-opacity'] > 0) {
2422						$fopacity = $current_style['fill-opacity'];
2423					} elseif ($current_style['fill-opacity'] < 0) {
2424						$fopacity = 0;
2425					}
2426				}
2427
2428				$sopacity = 1;
2429				if (isset($current_style['stroke-opacity'])) {
2430					if ($current_style['stroke-opacity'] == 0) {
2431						$sopacity = 0;
2432					} elseif ($current_style['stroke-opacity'] > 1) {
2433						$sopacity = 1;
2434					} elseif ($current_style['stroke-opacity'] > 0) {
2435						$sopacity = $current_style['stroke-opacity'];
2436					} elseif ($current_style['stroke-opacity'] < 0) {
2437						$sopacity = 0;
2438					}
2439				}
2440
2441				$gs = $this->mpdf->AddExtGState(['ca' => $fopacity, 'CA' => $sopacity, 'BM' => '/Normal']);
2442				$this->mpdf->extgstates[$gs]['fo'] = true;
2443				$opacitystr = sprintf(' /GS%d gs ', $gs);
2444
2445				$fillstr = '';
2446				if (isset($current_style['fill']) && $current_style['fill'] != 'none') {
2447					$col = $this->colorConverter->convert($current_style['fill'], $this->mpdf->PDFAXwarnings);
2448					$fillstr = $this->mpdf->SetFColor($col, true);
2449					$render = "0"; // Fill (only)
2450					$op = 'f';
2451				}
2452
2453				$strokestr = '';
2454				if ($stroke_width > 0 && $current_style['stroke'] != 'none') {
2455					$scol = $this->colorConverter->convert($current_style['stroke'], $this->mpdf->PDFAXwarnings);
2456					if ($scol) {
2457						$strokestr .= $this->mpdf->SetDColor($scol, true) . ' ';
2458					}
2459					$linewidth = $this->ConvertSVGSizePixels($stroke_width);
2460					if ($linewidth > 0) {
2461						$strokestr .= sprintf('%.3F w 0 J 0 j ', $linewidth * $this->kp);
2462						if ($render == -1) {
2463							$render = "1";
2464						} // stroke only
2465						else {
2466							$render = "2";
2467						}  // fill and stroke
2468						$op .= 'S';
2469					}
2470				}
2471
2472				if ($render == -1) {
2473					return '';
2474				}
2475
2476				if ($op == 'fS') {
2477					$op = 'B';
2478				}
2479
2480				$x = $this->txt_data[0]; // mPDF 5.7.4
2481				$y = $this->txt_data[1]; // mPDF 5.7.4
2482				$txt = $this->txt_data[2];
2483
2484				$txt = preg_replace('/\f/', '', $txt);
2485				$txt = preg_replace('/\r/', '', $txt);
2486				$txt = preg_replace('/\n/', ' ', $txt);
2487				$txt = preg_replace('/\t/', ' ', $txt);
2488				$txt = preg_replace("/[ ]+/u", ' ', $txt);
2489
2490				if ($this->textjuststarted) {
2491					$txt = ltrim($txt);
2492				}  // mPDF 5.7.4
2493
2494				$this->textjuststarted = false;  // mPDF 5.7.4
2495
2496				$txt = $this->mpdf->purify_utf8_text($txt);
2497
2498				if ($this->mpdf->text_input_as_HTML) {
2499					$txt = $this->mpdf->all_entities_to_utf8($txt);
2500				}
2501
2502				$nb = mb_strlen($txt, 'UTF-8');
2503				$i = 0;
2504				$sw = 0;
2505				$subpath_cmd = '';
2506
2507				while ($i < $nb) {
2508
2509					// Get next character
2510					$char = mb_substr($txt, $i, 1, 'UTF-8');
2511
2512
2513					if (isset($svg_font['glyphs'][$char])) {
2514						$d = $svg_font['glyphs'][$char]['d'];
2515						if (isset($svg_font['glyphs'][$char]['horiz-adv-x'])) {
2516							$horiz_adv_x = $svg_font['glyphs'][$char]['horiz-adv-x'];
2517						} else {
2518							$horiz_adv_x = $svg_font['horiz-adv-x'];
2519						} // missing glyph width
2520					} else {
2521						$d = $svg_font['d'];
2522						$horiz_adv_x = $svg_font['horiz-adv-x']; // missing glyph width
2523					}
2524
2525					preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $d, $commands, PREG_SET_ORDER);
2526					$subpath_cmd .= sprintf('q %.4F 0 0 %.4F mPDF-AXS(%.4F) %.4F cm ', $scale, -$scale, ($x + $sw * $scale) * $this->kp, -$y * $this->kp);
2527
2528					$this->subPathInit = true;
2529					$this->pathBBox = [999999, 999999, -999999, -999999];
2530
2531					foreach ($commands as $cmd) {
2532
2533						if (count($cmd) == 3 || (isset($cmd[2]) && $cmd[2] == '')) {
2534							list($tmp, $command, $arguments) = $cmd;
2535						} else {
2536							list($tmp, $command) = $cmd;
2537							$arguments = '';
2538						}
2539
2540						$subpath_cmd .= $this->svgPath($command, $arguments);
2541					}
2542
2543					$subpath_cmd .= $op . ' Q ';
2544
2545					if ($this->pathBBox[2] == -1999998) {
2546						$this->pathBBox[2] = 100;
2547					}
2548
2549					if ($this->pathBBox[3] == -1999998) {
2550						$this->pathBBox[3] = 100;
2551					}
2552
2553					if ($this->pathBBox[0] == 999999) {
2554						$this->pathBBox[0] = 0;
2555					}
2556
2557					if ($this->pathBBox[1] == 999999) {
2558						$this->pathBBox[1] = 0;
2559					}
2560
2561					$sw += $horiz_adv_x;
2562					$i++;
2563				}
2564
2565				$sw *= $scale; // convert stringwidth to units
2566				// mPDF 5.7.4
2567
2568				$this->textlength = $sw;
2569				$this->texttotallength += $this->textlength;
2570
2571				$path_cmd = sprintf('q %s %s Tr %s %s ', $opacitystr, $render, $fillstr, $strokestr);
2572				$path_cmd .= $subpath_cmd;
2573				$path_cmd .= 'Q ';
2574
2575				unset($this->txt_data[0], $this->txt_data[1], $this->txt_data[2]);
2576				return $path_cmd;
2577			}
2578
2579			// select font
2580			$style .= ($current_style['font-weight'] == 'bold') ? 'B' : '';
2581			$style .= ($current_style['font-style'] == 'italic') ? 'I' : '';
2582			$size = $current_style['font-size'] * $this->kf;
2583
2584			$current_style['font-family'] = $this->mpdf->SetFont($current_style['font-family'], $style, $size, false);
2585			$this->mpdf->CurrentFont['fo'] = true;
2586
2587			$opacitystr = '';
2588			// mPDF 6
2589			$fopacity = 1;
2590
2591			if (isset($current_style['fill-opacity'])) {
2592				if ($current_style['fill-opacity'] == 0) {
2593					$fopacity = 0;
2594				} elseif ($current_style['fill-opacity'] > 1) {
2595					$fopacity = 1;
2596				} elseif ($current_style['fill-opacity'] > 0) {
2597					$fopacity = $current_style['fill-opacity'];
2598				} elseif ($current_style['fill-opacity'] < 0) {
2599					$fopacity = 0;
2600				}
2601			}
2602
2603			$sopacity = 1;
2604
2605			if (isset($current_style['stroke-opacity'])) {
2606				if ($current_style['stroke-opacity'] == 0) {
2607					$sopacity = 0;
2608				} elseif ($current_style['stroke-opacity'] > 1) {
2609					$sopacity = 1;
2610				} elseif ($current_style['stroke-opacity'] > 0) {
2611					$sopacity = $current_style['stroke-opacity'];
2612				} elseif ($current_style['stroke-opacity'] < 0) {
2613					$sopacity = 0;
2614				}
2615			}
2616
2617			$gs = $this->mpdf->AddExtGState(['ca' => $fopacity, 'CA' => $sopacity, 'BM' => '/Normal']);
2618			$this->mpdf->extgstates[$gs]['fo'] = true;
2619			$opacitystr = sprintf(' /GS%d gs ', $gs);
2620
2621			$fillstr = '';
2622
2623			if (isset($current_style['fill']) && $current_style['fill'] != 'none') {
2624				$col = $this->colorConverter->convert($current_style['fill'], $this->mpdf->PDFAXwarnings);
2625				$fillstr = $this->mpdf->SetFColor($col, true);
2626				$render = "0"; // Fill (only)
2627			}
2628
2629			$strokestr = '';
2630
2631			if (isset($current_style['stroke-width']) && $current_style['stroke-width'] > 0 && $current_style['stroke'] != 'none') {
2632				$scol = $this->colorConverter->convert($current_style['stroke'], $this->mpdf->PDFAXwarnings);
2633
2634				if ($scol) {
2635					$strokestr .= $this->mpdf->SetDColor($scol, true) . ' ';
2636				}
2637
2638				$linewidth = $this->ConvertSVGSizePixels($current_style['stroke-width']);
2639
2640				if ($linewidth > 0) {
2641					$strokestr .= sprintf('%.3F w 1 J 1 j ', $linewidth * $this->kp);
2642					if ($render == -1) {
2643						$render = "1";
2644					} // stroke only
2645					else {
2646						$render = "2";
2647					}  // fill and stroke
2648				}
2649			}
2650
2651			if ($render == -1) {
2652				return '';
2653			}
2654
2655			$x = $this->txt_data[0]; // mPDF 5.7.4
2656			$y = $this->txt_data[1]; // mPDF 5.7.4
2657			$txt = $this->txt_data[2];
2658
2659			$txt = preg_replace('/\f/', '', $txt);
2660			$txt = preg_replace('/\r/', '', $txt);
2661			$txt = preg_replace('/\n/', ' ', $txt);
2662			$txt = preg_replace('/\t/', ' ', $txt);
2663			$txt = preg_replace("/[ ]+/u", ' ', $txt);
2664
2665			if ($this->textjuststarted) {
2666				$txt = ltrim($txt);
2667			}  // mPDF 5.7.4
2668
2669			$this->textjuststarted = false;  // mPDF 5.7.4
2670
2671			$txt = $this->mpdf->purify_utf8_text($txt);
2672
2673			if ($this->mpdf->text_input_as_HTML) {
2674				$txt = $this->mpdf->all_entities_to_utf8($txt);
2675			}
2676
2677			if ($this->mpdf->usingCoreFont) {
2678				$txt = mb_convert_encoding($txt, $this->mpdf->mb_enc, 'UTF-8');
2679			}
2680
2681			if (preg_match("/([" . $this->mpdf->pregRTLchars . "])/u", $txt)) {
2682				$this->mpdf->biDirectional = true;
2683			}
2684
2685			$textvar = 0;
2686			$save_OTLtags = $this->mpdf->OTLtags;
2687			$this->mpdf->OTLtags = [];
2688
2689			if ($this->mpdf->useKerning) {
2690
2691				if ($this->mpdf->CurrentFont['haskernGPOS']) {
2692
2693					if (isset($this->mpdf->OTLtags['Plus'])) {
2694						$this->mpdf->OTLtags['Plus'] .= ' kern';
2695					} else {
2696						$this->mpdf->OTLtags['Plus'] = ' kern';
2697					}
2698
2699				} else {
2700					$textvar = ($textvar | TextVars::FC_KERNING);
2701				}
2702			}
2703
2704			// Use OTL OpenType Table Layout - GSUB & GPOS
2705			if (isset($this->mpdf->CurrentFont['useOTL']) && $this->mpdf->CurrentFont['useOTL']) {
2706				$txt = $this->otl->applyOTL($txt, $this->mpdf->CurrentFont['useOTL']);
2707				$OTLdata = $this->otl->OTLdata;
2708			}
2709
2710			$this->mpdf->OTLtags = $save_OTLtags;
2711
2712			$this->mpdf->magic_reverse_dir($txt, $this->mpdf->directionality, $OTLdata);
2713
2714			$this->mpdf->CurrentFont['used'] = true;
2715
2716			$sw = $this->mpdf->GetStringWidth($txt, true, $OTLdata, $textvar); // also adds characters to subset
2717
2718			// mPDF 5.7.4
2719			$this->textlength = $sw * 1 / (25.4 / $this->mpdf->dpi);
2720			$this->texttotallength += $this->textlength;
2721
2722			$pdfx = $x * $this->kp;
2723			$pdfy = -$y * $this->kp;
2724
2725			$aixextra = sprintf(' /F%d %.3F Tf %s %s Tr %s %s ', $this->mpdf->CurrentFont['i'], $this->mpdf->FontSizePt, $opacitystr, $render, $fillstr, $strokestr);
2726
2727			$path_cmd = 'q 1 0 0 1 mPDF-AXS(0.00) 0 cm '; // Align X-shift
2728			$path_cmd .= $this->mpdf->Text($pdfx, $pdfy, $txt, $OTLdata, $textvar, $aixextra, 'SVG', true);
2729			$path_cmd .= " Q\n";
2730
2731			unset($this->txt_data[0], $this->txt_data[1], $this->txt_data[2]);
2732
2733			if (isset($current_style['font-size-parent'])) {
2734				$this->mpdf->SetFontSize($current_style['font-size-parent']);
2735			}
2736
2737		} else {
2738			return ' ';
2739		}
2740
2741		// Reset font	// mPDF 5.7.4
2742		$prev_style = $this->txt_style[count($this->txt_style) - 1];
2743		$style = '';
2744		$style .= ($prev_style['font-weight'] == 'bold') ? 'B' : '';
2745		$style .= ($prev_style['font-style'] == 'italic') ? 'I' : '';
2746		$size = $prev_style['font-size'] * $this->kf;
2747		$this->mpdf->SetFont($prev_style['font-family'], $style, $size, false);
2748
2749		return $path_cmd;
2750	}
2751
2752	function svgDefineTxtStyle($critere_style)
2753	{
2754		// get copy of current/default txt style, and modify it with supplied attributes
2755		$tmp = count($this->txt_style) - 1;
2756		$current_style = $this->txt_style[$tmp];
2757
2758		if (isset($critere_style['style'])) {
2759
2760			if (preg_match('/fill:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) {
2761				$current_style['fill'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT);
2762			} else {
2763				$tmp = preg_replace("/(.*)fill:\s*([a-z0-9#_()]*|none)(.*)/i", "$2", $critere_style['style']);
2764				if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2765					$current_style['fill'] = $tmp;
2766				}
2767			}
2768
2769			// mPDF 6
2770			if (preg_match("/[^-]opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m) ||
2771				preg_match("/^opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m)) {
2772				$current_style['fill-opacity'] = $m[1];
2773				$current_style['stroke-opacity'] = $m[1];
2774			}
2775
2776			$tmp = preg_replace("/(.*)fill-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
2777			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2778				$current_style['fill-opacity'] = $tmp;
2779			}
2780
2781			$tmp = preg_replace("/(.*)fill-rule:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
2782			if ($tmp != $critere_style['style'] && $tmp != $critere_style['style']) {
2783				$current_style['fill-rule'] = $tmp;
2784			}
2785
2786			if (preg_match('/stroke:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) {
2787				$current_style['stroke'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT);
2788			} else {
2789				$tmp = preg_replace("/(.*)stroke:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
2790				if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2791					$current_style['stroke'] = $tmp;
2792				}
2793			}
2794
2795			$tmp = preg_replace("/(.*)stroke-linecap:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
2796			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2797				$current_style['stroke-linecap'] = $tmp;
2798			}
2799
2800			$tmp = preg_replace("/(.*)stroke-linejoin:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
2801			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2802				$current_style['stroke-linejoin'] = $tmp;
2803			}
2804
2805			$tmp = preg_replace("/(.*)stroke-miterlimit:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']);
2806			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2807				$current_style['stroke-miterlimit'] = $tmp;
2808			}
2809
2810			$tmp = preg_replace("/(.*)stroke-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
2811			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2812				$current_style['stroke-opacity'] = $tmp;
2813			}
2814
2815			$tmp = preg_replace("/(.*)stroke-width:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
2816			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2817				$current_style['stroke-width'] = $tmp;
2818			}
2819
2820			$tmp = preg_replace("/(.*)stroke-dasharray:\s*([a-z0-9., ]*|none)(.*)/i", "$2", $critere_style['style']);
2821			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2822				$current_style['stroke-dasharray'] = $tmp;
2823			}
2824
2825			$tmp = preg_replace("/(.*)stroke-dashoffset:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
2826			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2827				$current_style['stroke-dashoffset'] = $tmp;
2828			}
2829
2830			$tmp = preg_replace("/(.*)font-family:\s*([a-z0-9.\"' ,\-]*|none)(.*)/i", "$2", $critere_style['style']);
2831			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2832				$critere_style['font-family'] = $tmp;
2833			}
2834
2835			$tmp = preg_replace("/(.*)font-size:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']);
2836			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2837				$critere_style['font-size'] = $tmp;
2838			}
2839
2840			$tmp = preg_replace("/(.*)font-weight:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']);
2841			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2842				$critere_style['font-weight'] = $tmp;
2843			}
2844
2845			$tmp = preg_replace("/(.*)font-style:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']);
2846			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2847				$critere_style['font-style'] = $tmp;
2848			}
2849
2850			$tmp = preg_replace("/(.*)font-variant:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']);
2851			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2852				$critere_style['font-variant'] = $tmp;
2853			}
2854
2855			$tmp = preg_replace("/(.*)text-anchor:\s*(start|middle|end)(.*)/i", "$2", $critere_style['style']);
2856			if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) {
2857				$critere_style['text-anchor'] = $tmp;
2858			}
2859		}
2860
2861		if (isset($critere_style['font'])) {
2862			// [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]?<'font-size'> [ / <'line-height'> ]? <'font-family'> ]
2863
2864			$tmp = preg_replace("/(.*)(italic|oblique)(.*)/i", "$2", $critere_style['font']);
2865			if ($tmp != $critere_style['font']) {
2866				if ($tmp == 'oblique') {
2867					$tmp = 'italic';
2868				}
2869				$current_style['font-style'] = $tmp;
2870			}
2871
2872			$tmp = preg_replace("/(.*)(bold|bolder)(.*)/i", "$2", $critere_style['font']);
2873			if ($tmp != $critere_style['font']) {
2874				if ($tmp == 'bolder') {
2875					$tmp = 'bold';
2876				}
2877				$current_style['font-weight'] = $tmp;
2878			}
2879
2880			$tmp = preg_replace("/(.*)(small\-caps)(.*)/i", "$2", $critere_style['font']);
2881			if ($tmp != $critere_style['font']) {
2882				$current_style['font-variant'] = $tmp;
2883			}
2884
2885			// select digits not followed by percent sign nor preceeded by forward slash
2886			$tmp = preg_replace("/(.*)\b(\d+)[\b|\/](.*)/i", "$2", $critere_style['font']);
2887			if ($tmp != $critere_style['font']) {
2888				$current_style['font-size'] = $this->ConvertSVGSizePts($tmp);
2889				$this->mpdf->SetFont('', '', $current_style['font-size'], false);
2890			}
2891		}
2892
2893		// mPDF 6
2894		if (isset($critere_style['opacity']) && $critere_style['opacity'] != 'inherit') {
2895			$current_style['fill-opacity'] = $critere_style['opacity'];
2896			$current_style['stroke-opacity'] = $critere_style['opacity'];
2897		}
2898
2899		// mPDF 6
2900		if (isset($critere_style['stroke-opacity']) && $critere_style['stroke-opacity'] != 'inherit') {
2901			$current_style['stroke-opacity'] = $critere_style['stroke-opacity'];
2902		}
2903
2904		// mPDF 6
2905		if (isset($critere_style['fill-opacity']) && $critere_style['fill-opacity'] != 'inherit') {
2906			$current_style['fill-opacity'] = $critere_style['fill-opacity'];
2907		}
2908
2909		if (isset($critere_style['fill']) && $critere_style['fill'] != 'inherit') {
2910			$current_style['fill'] = $critere_style['fill'];
2911		}
2912
2913		if (isset($critere_style['stroke']) && $critere_style['stroke'] != 'inherit') {
2914			$current_style['stroke'] = $critere_style['stroke'];
2915		}
2916
2917		if (isset($critere_style['stroke-width']) && $critere_style['stroke-width'] != 'inherit') {
2918			$current_style['stroke-width'] = $critere_style['stroke-width'];
2919		}
2920
2921		if (isset($critere_style['font-style']) && $critere_style['font-style'] != 'inherit') {
2922			if (strtolower($critere_style['font-style']) == 'oblique') {
2923				$critere_style['font-style'] = 'italic';
2924			}
2925			$current_style['font-style'] = $critere_style['font-style'];
2926		}
2927
2928		if (isset($critere_style['font-weight']) && $critere_style['font-weight'] != 'inherit') {
2929			if (strtolower($critere_style['font-weight']) == 'bolder') {
2930				$critere_style['font-weight'] = 'bold';
2931			}
2932			$current_style['font-weight'] = $critere_style['font-weight'];
2933		}
2934
2935		if (isset($critere_style['font-variant']) && $critere_style['font-variant'] != 'inherit') {
2936			$current_style['font-variant'] = $critere_style['font-variant'];
2937		}
2938
2939		if (isset($critere_style['font-size']) && $critere_style['font-size'] != 'inherit') {
2940			if (strpos($critere_style['font-size'], '%') !== false) {
2941				$current_style['font-size-parent'] = $current_style['font-size'];
2942			}
2943			$current_style['font-size'] = $this->ConvertSVGSizePts($critere_style['font-size']);
2944			$this->mpdf->SetFont('', '', $current_style['font-size'], false);
2945		}
2946
2947		if (isset($critere_style['font-family']) && $critere_style['font-family'] != 'inherit') {
2948			$v = $critere_style['font-family'];
2949			$aux_fontlist = explode(",", $v);
2950			$found = 0;
2951
2952			$svgfontstyle = 'R';
2953			$svgfontstyle .= (isset($current_style['font-weight']) && $current_style['font-weight'] == 'bold') ? 'B' : '';
2954			$svgfontstyle .= (isset($current_style['font-style']) && $current_style['font-style'] == 'italic') ? 'I' : '';
2955			$svgfontstyle .= (isset($current_style['font-variant']) && $current_style['font-variant'] == 'small-caps') ? 'S' : '';
2956
2957			foreach ($aux_fontlist as $f) {
2958				$fonttype = trim($f);
2959				$fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
2960				$fonttype = preg_replace('/ /', '', $fonttype);
2961				$v = strtolower(trim($fonttype));
2962				if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) {
2963					$v = $this->mpdf->fonttrans[$v];
2964				}
2965				if ((!$this->mpdf->usingCoreFont && in_array($v, $this->mpdf->available_unifonts)) ||
2966					($this->mpdf->usingCoreFont && in_array($v, ['courier', 'times', 'helvetica', 'arial'])) ||
2967					in_array($v, ['sjis', 'uhc', 'big5', 'gb']) || isset($this->svg_font[$v][$svgfontstyle])) { // mPDF 6
2968					$current_style['font-family'] = $v;
2969					$found = 1;
2970					break;
2971				}
2972			}
2973
2974			if (!$found) {
2975				foreach ($aux_fontlist as $f) {
2976					$fonttype = trim($f);
2977					$fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
2978					$fonttype = preg_replace('/ /', '', $fonttype);
2979					$v = strtolower(trim($fonttype));
2980					if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) {
2981						$v = $this->mpdf->fonttrans[$v];
2982					}
2983					if (in_array($v, $this->mpdf->sans_fonts) || in_array($v, $this->mpdf->serif_fonts) || in_array($v, $this->mpdf->mono_fonts) || isset($this->svg_font[$v][$svgfontstyle])) {  // mPDF 6
2984						$current_style['font-family'] = $v;
2985						break;
2986					}
2987				}
2988			}
2989		}
2990
2991		if (isset($critere_style['text-anchor']) && $critere_style['text-anchor'] != 'inherit') {
2992			$current_style['text-anchor'] = $critere_style['text-anchor'];
2993		}
2994
2995		// add current style to text style array (will remove it later after writing text to svg_string)
2996		array_push($this->txt_style, $current_style);
2997	}
2998
2999	function svgAddGradient($id, $array_gradient)
3000	{
3001
3002		$this->svg_gradient[$id] = $array_gradient;
3003	}
3004
3005	function svgWriteString($content)
3006	{
3007		$this->svg_string .= $content;
3008	}
3009
3010	/**
3011	 * SVGs made with Adobe Illustrator use a style tag and classes instead of inline styles
3012	 * See: https://github.com/mpdf/mpdf/issues/450
3013	 *
3014	 * This function brutally copies the styles inline
3015	 *  ( Currently only looks for classes as a selector )
3016	 *
3017	 * @param string $data svg contents
3018	 * @return string svg contents
3019	 * @author Antonio Norman - softcodex.ch
3020	 */
3021	function mergeStyles($data)
3022	{
3023		$xml = new \DOMDocument();
3024		if (!$xml->loadXML($data, LIBXML_NOERROR)) {
3025			return $data;
3026		}
3027
3028		// Check it's an SVG
3029		$svgNode = $xml->getElementsByTagName('svg');
3030		if ($svgNode->length === 0) {
3031			return $data;
3032		}
3033
3034		// Find the style node
3035		$styles = [];
3036		/** @var $styleNode \DOMNode */
3037		foreach ($svgNode->item(0)->getElementsByTagName('style') as $styleNode) {
3038
3039			preg_match_all('/(\.[^{]+)\s*\{\s*([^}]+)\s*}/m', $styleNode->nodeValue, $matches, PREG_SET_ORDER);
3040			foreach ($matches as $cssBlock) {
3041				$css = preg_replace('/\s{2,}/', ' ', $cssBlock[2]); // Clean spaces or new lines
3042				$selector = trim($cssBlock[1]);
3043
3044				$styles[$selector] = isset($styles[$cssBlock[1]]) ?
3045					$styles[$selector] . ' ' . $css : // Append if the selector is already defined
3046					$css;
3047			}
3048		}
3049
3050		if (empty($styles)) {
3051			return $data;
3052		}
3053
3054		// Recursively loop the nodes inserting the styles inline
3055		$setStylesInline = function (\DOMNode $xml) use ($styles, &$setStylesInline) {
3056			// Apply the styles to the elements
3057			foreach ($xml->childNodes as $node) {
3058
3059				if ($node->hasChildNodes()) {
3060					$setStylesInline($node);
3061				}
3062
3063				if (!$node instanceof \DOMElement) {
3064					continue;
3065				}
3066
3067				// Check the node has the a class with a style
3068				if (!$node->hasAttribute('class')) {
3069					continue;
3070				}
3071
3072				// Allow for class=" class1  class2 "
3073				$classes = explode(' ', $node->getAttribute('class'));
3074
3075				foreach ($classes as $class) {
3076
3077					$class = '.' . trim($class);
3078					if (!empty($class) && isset($styles[$class])) {
3079
3080						$style = $node->hasAttribute('style') ?
3081							$styles[$class] . ' ' . $node->getAttribute('style') :
3082							$styles[$class];
3083
3084						$node->setAttribute('style', $style);
3085					}
3086				}
3087			}
3088		};
3089
3090		$setStylesInline($xml);
3091
3092		return $xml->saveXML();
3093	}
3094
3095	/**
3096	 * analise le svg et renvoie aux fonctions precedente our le traitement
3097	 */
3098	function ImageSVG($data)
3099	{
3100		$data = preg_replace('/^.*?<svg([> ])/is', '<svg\\1', $data); // mPDF 5.7.4
3101		$data = preg_replace('/<!--.*?-->/is', '', $data); // mPDF 5.7.4
3102
3103		// Converts < to &lt; when not a tag
3104		$data = preg_replace('/<([^!?\/a-zA-Z_:])/i', '&lt;\\1', $data); // mPDF 5.7.4
3105
3106		$data = $this->mergeStyles($data);
3107
3108		if ($this->mpdf->svgAutoFont) {
3109			$data = $this->markScriptToLang($data);
3110		}
3111
3112		$this->svg_info = [];
3113		$last_gradid = ''; // mPDF 6
3114		$last_svg_fontid = ''; // mPDF 6
3115		$last_svg_fontdefw = ''; // mPDF 6
3116		$last_svg_fontstyle = ''; // mPDF 6
3117
3118		if (preg_match('/<!ENTITY/si', $data)) {
3119			// Get User-defined entities
3120			preg_match_all('/<!ENTITY\s+([a-z]+)\s+\"(.*?)\">/si', $data, $ent);
3121			// Replace entities
3122			for ($i = 0; $i < count($ent[0]); $i++) {
3123				$data = preg_replace('/&' . preg_quote($ent[1][$i], '/') . ';/is', $ent[2][$i], $data);
3124			}
3125		}
3126
3127		if (preg_match('/xlink:href\s*=/si', $data)) {
3128
3129			// GRADIENTS
3130			// Get links
3131			preg_match_all('/(<(linearGradient|radialgradient)[^>]*)xlink:href\s*=\s*["\']#(.*?)["\'](.*?)\/>/si', $data, $links);
3132			if (count($links[0])) {
3133				$links[5] = [];
3134			}
3135
3136			// Delete links from data - keeping in $links
3137			for ($i = 0; $i < count($links[0]); $i++) {
3138				$links[5][$i] = 'tmpLink' . random_int(100000, 9999999);
3139				$data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', '<MYLINKS' . $links[5][$i] . '>', $data);
3140			}
3141
3142			// Get targets
3143			preg_match_all('/<(linearGradient|radialgradient)([^>]*)id\s*=\s*["\'](.*?)["\'](.*?)>(.*?)<\/(linearGradient|radialgradient)>/si', $data, $m);
3144			$targets = [];
3145			$stops = [];
3146
3147			// keeping in $targets
3148			for ($i = 0; $i < count($m[0]); $i++) {
3149				$stops[$m[3][$i]] = $m[5][$i];
3150			}
3151
3152			// Add back links this time as targets (gradients)
3153			for ($i = 0; $i < count($links[0]); $i++) {
3154				$def = $links[1][$i] . ' ' . $links[4][$i] . '>' . $stops[$links[3][$i]] . '</' . $links[2][$i] . '>';
3155				$data = preg_replace('/<MYLINKS' . $links[5][$i] . '>/is', $def, $data);
3156			}
3157
3158			// mPDF 5.7.4
3159			// <TREF>
3160			preg_match_all('/<tref ([^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)\/>/si', $data, $links);
3161			for ($i = 0; $i < count($links[0]); $i++) {
3162				// Get the item to use from defs
3163				$insert = '';
3164				if (preg_match('/<text [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>(.*?)<\/text>/si', $data, $m)) {
3165					$insert = $m[1];
3166				}
3167				if ($insert) {
3168					$data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $insert, $data);
3169				}
3170			}
3171
3172			// mPDF 5.7.2
3173			// <USE>
3174			preg_match_all('/<use( [^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)\/>/si', $data, $links);
3175			for ($i = 0; $i < count($links[0]); $i++) {
3176				// Get the item to use from defs
3177				$insert = '';
3178				if (preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*\/>/si', $data, $m)) {
3179					$insert = $m[0];
3180				}
3181				if (!$insert && preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\']/si', $data, $m)) {
3182					if (preg_match('/<' . $m[1] . '[^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>.*?<\/' . $m[1] . '>/si', $data, $m)) {
3183						$insert = $m[0];
3184					}
3185				}
3186
3187				if ($insert) {
3188					$inners = $links[1][$i] . ' ' . $links[3][$i];
3189
3190					// Change x,y coords to translate()
3191					if (preg_match('/\sy\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3192						$y = $m[1];
3193					} else {
3194						$y = 0;
3195					}
3196
3197					if (preg_match('/\sx\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3198						$x = $m[1];
3199					} else {
3200						$x = 0;
3201					}
3202
3203					if ($x || $y) {
3204
3205						$inners = preg_replace('/(y|x)\s*=\s*["\']([^>]*?)["\']/', '', $inners);
3206						if (preg_match('/transform\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3207
3208							if (preg_match('/translate\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/', $m[1], $mm)) {
3209								$transform = $m[1]; // transform="...."
3210								$x += $mm[1];
3211								$y += $mm[2];
3212								$transform = preg_replace('/' . preg_quote($mm[0], '/') . '/', '', $transform);
3213								$transform = 'transform="' . $transform . ' translate(' . $x . ', ' . $y . ')"';
3214								$inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', $transform, $inners);
3215							} else {
3216								$inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', 'transform="' . $m[1] . ' translate(' . $x . ', ' . $y . ')"', $inners);
3217							}
3218
3219						} else {
3220							$inners .= ' transform="translate(' . $x . ', ' . $y . ')"';
3221						}
3222					}
3223				}
3224
3225				$replacement = '<g ' . $inners . '>' . $insert . '</g>';
3226				$data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $replacement, $data);
3227			}
3228
3229			preg_match_all('/<use( [^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)>\s*<\/use>/si', $data, $links);
3230			for ($i = 0; $i < count($links[0]); $i++) {
3231
3232				// Get the item to use from defs
3233				$insert = '';
3234				if (preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*\/>/si', $data, $m)) {
3235					$insert = $m[0];
3236				}
3237
3238				if (!$insert && preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\']/si', $data, $m)) {
3239					if (preg_match('/<' . $m[1] . '[^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>.*?<\/' . $m[1] . '>/si', $data, $m)) {
3240						$insert = $m[0];
3241					}
3242				}
3243
3244				if ($insert) {
3245					$inners = $links[1][$i] . ' ' . $links[3][$i];
3246
3247					// Change x,y coords to translate()
3248					if (preg_match('/\sy\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3249						$y = $m[1];
3250					} else {
3251						$y = 0;
3252					}
3253
3254					if (preg_match('/\sx\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3255						$x = $m[1];
3256					} else {
3257						$x = 0;
3258					}
3259
3260					if ($x || $y) {
3261						$inners = preg_replace('/(y|x)\s*=\s*["\']([^>]*?)["\']/', '', $inners);
3262						if (preg_match('/transform\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) {
3263							if (preg_match('/translate\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/', $m[1], $mm)) {
3264								$transform = $m[1]; // transform="...."
3265								$x += $mm[1];
3266								$y += $mm[2];
3267								$transform = preg_replace('/' . preg_quote($mm[0], '/') . '/', '', $transform);
3268								$transform = 'transform="' . $transform . ' translate(' . $x . ', ' . $y . ')"';
3269								$inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', $transform, $inners);
3270							} else {
3271								$inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', 'transform="' . $m[1] . ' translate(' . $x . ', ' . $y . ')"', $inners);
3272							}
3273						} else {
3274							$inners .= ' transform="translate(' . $x . ', ' . $y . ')"';
3275						}
3276					}
3277
3278					$replacement = '<g ' . $inners . '>' . $insert . '</g>';
3279					$data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $replacement, $data);
3280				}
3281			}
3282		}
3283
3284		// Removes <pattern>
3285		$data = preg_replace('/<pattern.*?<\/pattern>/is', '', $data);
3286
3287		// Removes <marker>
3288		$data = preg_replace('/<marker.*?<\/marker>/is', '', $data);
3289
3290		$this->svg_info['data'] = $data;
3291		$this->svg_string = '';
3292
3293		$svg2pdf_xml = '';
3294
3295		// Don't output stuff inside <defs>
3296		$this->inDefs = false;
3297
3298		$svg2pdf_xml_parser = xml_parser_create("utf-8");
3299
3300		xml_parser_set_option($svg2pdf_xml_parser, XML_OPTION_CASE_FOLDING, false);
3301
3302		xml_set_element_handler(
3303			$svg2pdf_xml_parser,
3304			[$this, 'xml_svg2pdf_start'],
3305			[$this, 'xml_svg2pdf_end']
3306		);
3307
3308		xml_set_character_data_handler(
3309			$svg2pdf_xml_parser,
3310			[$this, 'characterData']
3311		);
3312
3313		xml_parse($svg2pdf_xml_parser, $data);
3314
3315		if ($this->svg_error) {
3316			return false;
3317		} else {
3318			return [
3319				'x' => $this->svg_info['x'] * $this->kp,
3320				'y' => -$this->svg_info['y'] * $this->kp,
3321				'w' => $this->svg_info['w'] * $this->kp,
3322				'h' => -$this->svg_info['h'] * $this->kp,
3323				'data' => $this->svg_string,
3324			];
3325		}
3326	}
3327
3328	// AUTOFONT =========================
3329	/** @todo reuse as much code from Mpdf::markScriptToLang as possible */
3330	function markScriptToLang($html)
3331	{
3332		if ($this->mpdf->onlyCoreFonts) {
3333			return $html;
3334		}
3335
3336		$n = '';
3337		$a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
3338		foreach ($a as $i => $e) {
3339			if ($i % 2 == 0) {
3340				$e = UtfString::strcode2utf($e);
3341				$e = $this->mpdf->lesser_entity_decode($e);
3342
3343				$earr = $this->mpdf->UTF8StringToArray($e, false);
3344
3345				$scriptblock = 0;
3346				$scriptblocks = [];
3347				$scriptblocks[0] = 0;
3348				$chardata = [];
3349				$subchunk = 0;
3350				$charctr = 0;
3351				foreach ($earr as $char) {
3352					$ucd_record = Ucdn::get_ucd_record($char);
3353					$sbl = $ucd_record[6];
3354
3355					if ($sbl && $sbl != 40 && $sbl != 102) {
3356						if ($scriptblock == 0) {
3357							$scriptblock = $sbl;
3358							$scriptblocks[$subchunk] = $scriptblock;
3359						} elseif ($scriptblock > 0 && $scriptblock != $sbl) {
3360							// NEW (non-common) Script encountered in this chunk.
3361							// Start a new subchunk
3362							$subchunk++;
3363							$scriptblock = $sbl;
3364							$charctr = 0;
3365							$scriptblocks[$subchunk] = $scriptblock;
3366						}
3367					}
3368
3369					$chardata[$subchunk][$charctr]['script'] = $sbl;
3370					$chardata[$subchunk][$charctr]['uni'] = $char;
3371					$charctr++;
3372				}
3373
3374				// If scriptblock[x] = common & non-baseScript
3375				// and scriptblock[x+1] = baseScript
3376				// Move common script from end of x to start of x+1
3377				for ($sch = 0; $sch < $subchunk; $sch++) {
3378					if ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->mpdf->baseScript && $scriptblocks[$sch + 1] == $this->mpdf->baseScript) {
3379						$end = count($chardata[$sch]) - 1;
3380						while ($chardata[$sch][$end]['script'] == 0 && $end > 1) { // common script
3381							$tmp = array_pop($chardata[$sch]);
3382							array_unshift($chardata[$sch + 1], $tmp);
3383							$end--;
3384						}
3385					}
3386				}
3387
3388				$o = '';
3389				for ($sch = 0; $sch <= $subchunk; $sch++) {
3390					if (isset($chardata[$sch])) {
3391						$s = '';
3392						for ($j = 0; $j < count($chardata[$sch]); $j++) {
3393							$s .= UtfString::code2utf($chardata[$sch][$j]['uni']);
3394						}
3395						// ZZZ99 Undo lesser_entity_decode as above - but only for <>&
3396						$s = str_replace("&", "&amp;", $s);
3397						$s = str_replace("<", "&lt;", $s);
3398						$s = str_replace(">", "&gt;", $s);
3399
3400						if (substr($a[$i - 1], 0, 5) != '<text' && substr($a[$i - 1], 0, 5) != '<tspa') {
3401							continue;
3402						} // <tspan> or <text> only
3403
3404						$lang = '';
3405						// Check Vietnamese if Latin script - even if Basescript
3406						if ($scriptblocks[$sch] == Ucdn::SCRIPT_LATIN && $this->mpdf->autoVietnamese && preg_match("/([" . $this->scriptToLanguage->getLanguageDelimiters('viet') . "])/u", $s)) {
3407							$lang = "vi";
3408						} // Check Arabic for different languages if Arabic script - even if Basescript
3409						elseif ($scriptblocks[$sch] == Ucdn::SCRIPT_ARABIC && $this->mpdf->autoArabic) {
3410							if (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('sindhi') . "]/u", $s)) {
3411								$lang = "sd";
3412							} elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('urdu') . "]/u", $s)) {
3413								$lang = "ur";
3414							} elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('pashto') . "]/u", $s)) {
3415								$lang = "ps";
3416							} elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('persian') . "]/u", $s)) {
3417								$lang = "fa";
3418							} elseif ($this->mpdf->baseScript != Ucdn::SCRIPT_ARABIC && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) {
3419								$lang = "'." . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . "'";
3420							}
3421						} // Identify Script block if not Basescript, and mark up as language
3422						elseif ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->mpdf->baseScript && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) {
3423							$lang = $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]);
3424						}
3425						if ($lang) {
3426							$o .= '<tspan lang="' . $lang . '">' . $s . '</tspan>';
3427						} else {
3428							$o .= $s;
3429						}
3430					}
3431				}
3432
3433				$a[$i] = $o;
3434			} else {
3435				$a[$i] = '<' . $e . '>';
3436			}
3437		}
3438		$n = implode('', $a);
3439		return $n;
3440	}
3441
3442	function xml_svg2pdf_start($parser, $name, $attribs)
3443	{
3444		global $last_gradid, $last_svg_fontid, $last_svg_fontdefw, $last_svg_fontstyle; // mPDF 6
3445
3446		// mPDF 6
3447		if (strtolower($name) == 'font') {
3448			$last_svg_fontid = '';
3449
3450			if (isset($attribs['horiz-adv-x']) && $attribs['horiz-adv-x']) {
3451				$last_svg_fontdefw = $attribs['horiz-adv-x'];
3452			}
3453
3454			return;
3455		} elseif (strtolower($name) == 'font-face') { // mPDF 6
3456			$last_svg_fontstyle = 'R';
3457			$last_svg_fontstyle .= (isset($attribs['font-weight']) && $attribs['font-weight'] == 'bold') ? 'B' : '';
3458			$last_svg_fontstyle .= (isset($attribs['font-style']) && $attribs['font-style'] == 'italic') ? 'I' : '';
3459			$last_svg_fontstyle .= (isset($attribs['font-variant']) && $attribs['font-variant'] == 'small-caps') ? 'S' : '';
3460
3461			if (isset($attribs['font-family']) && $attribs['font-family']) {
3462
3463				$tmp_svg_font = [
3464					'units-per-em' => (isset($attribs['units-per-em']) ? $attribs['units-per-em'] : ''),
3465					'd' => '',
3466					'glyphs' => []
3467				];
3468
3469				$last_svg_fontid = strtolower($attribs['font-family']);
3470
3471				if ($last_svg_fontdefw) {
3472					$tmp_svg_font['horiz-adv-x'] = $last_svg_fontdefw;
3473				} else {
3474					$tmp_svg_font['horiz-adv-x'] = 500;
3475				}
3476
3477				$this->svg_font[$last_svg_fontid][$last_svg_fontstyle] = $tmp_svg_font;
3478			}
3479
3480			return;
3481		} elseif (strtolower($name) == 'missing-glyph') { // mPDF 6
3482
3483			if ($last_svg_fontid && isset($attribs['horiz-adv-x'])) {
3484				$this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['horiz-adv-x'] = (isset($attribs['horiz-adv-x']) ? $attribs['horiz-adv-x'] : '');
3485				$this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['d'] = (isset($attribs['d']) ? $attribs['d'] : '');
3486			}
3487
3488			return;
3489
3490		} elseif (strtolower($name) == 'glyph') { // mPDF 6
3491
3492			if ($last_svg_fontid && isset($attribs['unicode'])) {
3493				$this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['glyphs'][$attribs['unicode']] = [
3494					'horiz-adv-x' => (isset($attribs['horiz-adv-x']) ? $attribs['horiz-adv-x'] : $last_svg_fontdefw),
3495					'd' => (isset($attribs['d']) ? $attribs['d'] : ''),
3496				];
3497			}
3498			return;
3499
3500		} elseif (strtolower($name) == 'lineargradient') { // mPDF 5.7.2
3501
3502			$tmp_gradient = [
3503				'type' => 'linear',
3504				'transform' => (isset($attribs['gradientTransform']) ? $attribs['gradientTransform'] : ''),
3505				'units' => (isset($attribs['gradientUnits']) ? $attribs['gradientUnits'] : ''),
3506				'spread' => (isset($attribs['spreadMethod']) ? $attribs['spreadMethod'] : ''),
3507				'color' => []
3508			];
3509
3510			if (isset($attribs['x1'])) {
3511				$tmp_gradient['info']['x1'] = $attribs['x1'];
3512			}
3513
3514			if (isset($attribs['y1'])) {
3515				$tmp_gradient['info']['y1'] = $attribs['y1'];
3516			}
3517
3518			if (isset($attribs['x2'])) {
3519				$tmp_gradient['info']['x2'] = $attribs['x2'];
3520			}
3521
3522			if (isset($attribs['y2'])) {
3523				$tmp_gradient['info']['y2'] = $attribs['y2'];
3524			}
3525
3526			$last_gradid = $attribs['id'];
3527			$this->svgAddGradient($attribs['id'], $tmp_gradient);
3528
3529			return;
3530
3531		} elseif (strtolower($name) == 'radialgradient') {
3532
3533			$tmp_gradient = [
3534				'type' => 'radial',
3535				'transform' => (isset($attribs['gradientTransform']) ? $attribs['gradientTransform'] : ''),
3536				'units' => (isset($attribs['gradientUnits']) ? $attribs['gradientUnits'] : ''),
3537				'spread' => (isset($attribs['spreadMethod']) ? $attribs['spreadMethod'] : ''),
3538				'color' => []
3539			];
3540
3541			if (isset($attribs['cx'])) {
3542				$tmp_gradient['info']['x0'] = $attribs['cx'];
3543			}
3544
3545			if (isset($attribs['cy'])) {
3546				$tmp_gradient['info']['y0'] = $attribs['cy'];
3547			}
3548
3549			if (isset($attribs['fx'])) {
3550				$tmp_gradient['info']['x1'] = $attribs['fx'];
3551			}
3552
3553			if (isset($attribs['fy'])) {
3554				$tmp_gradient['info']['y1'] = $attribs['fy'];
3555			}
3556
3557			if (isset($attribs['r'])) {
3558				$tmp_gradient['info']['r'] = $attribs['r'];
3559			}
3560
3561			$last_gradid = $attribs['id'];
3562			$this->svgAddGradient($attribs['id'], $tmp_gradient);
3563
3564			return;
3565
3566		} elseif (strtolower($name) == 'stop') {
3567
3568			if (!$last_gradid) {
3569				return;
3570			}
3571
3572			$color = '#000000';
3573			if (isset($attribs['style']) and preg_match('/stop-color:\s*([^;]*)/i', $attribs['style'], $m)) {
3574				$color = trim($m[1]);
3575			} elseif (isset($attribs['stop-color']) && $attribs['stop-color']) {
3576				$color = $attribs['stop-color'];
3577			}
3578
3579			$col = $this->colorConverter->convert($color, $this->mpdf->PDFAXwarnings);
3580			if (!$col) {
3581				$col = $this->colorConverter->convert('#000000', $this->mpdf->PDFAXwarnings);
3582			} // In case "transparent" or "inherit" returned
3583
3584			if ($col[0] == 3 || $col[0] == 5) { // RGB
3585				$color_final = sprintf('%.3F %.3F %.3F', ord($col[1]) / 255, ord($col[2]) / 255, ord($col[3]) / 255);
3586				$this->svg_gradient[$last_gradid]['colorspace'] = 'RGB';
3587			} elseif ($col[0] == 4 || $col[0] == 6) { // CMYK
3588				$color_final = sprintf('%.3F %.3F %.3F %.3F', ord($col[1]) / 100, ord($col[2]) / 100, ord($col[3]) / 100, ord($col[4]) / 100);
3589				$this->svg_gradient[$last_gradid]['colorspace'] = 'CMYK';
3590			} elseif ($col[0] == 1) { // Grayscale
3591				$color_final = sprintf('%.3F', ord($col[1]) / 255);
3592				$this->svg_gradient[$last_gradid]['colorspace'] = 'Gray';
3593			}
3594
3595			$stop_opacity = 1;
3596
3597			if (isset($attribs['style']) and preg_match('/stop-opacity:\s*([0-9.]*)/i', $attribs['style'], $m)) {
3598				$stop_opacity = $m[1];
3599			} elseif (isset($attribs['stop-opacity'])) {
3600				$stop_opacity = $attribs['stop-opacity'];
3601			} elseif ($col[0] == 5) { // RGBa
3602				$stop_opacity = ord($col[4] / 100);
3603			} elseif ($col[0] == 6) { // CMYKa
3604				$stop_opacity = ord($col[5] / 100);
3605			}
3606
3607			$tmp_color = [
3608				'color' => $color_final,
3609				'offset' => (isset($attribs['offset']) ? $attribs['offset'] : ''),
3610				'opacity' => $stop_opacity
3611			];
3612			array_push($this->svg_gradient[$last_gradid]['color'], $tmp_color);
3613
3614			return;
3615		}
3616
3617		if ($this->inDefs) {
3618			return;
3619		}
3620
3621		$this->xbase = 0;
3622		$this->ybase = 0;
3623
3624		switch (strtolower($name)) {
3625
3626			// Don't output stuff inside <defs>
3627			case 'defs':
3628				$this->inDefs = true;
3629				return;
3630
3631			case 'svg':
3632				$this->svgOffset($attribs);
3633				break;
3634
3635			case 'path':
3636
3637				$path = Arrays::get($attribs, 'd', '');
3638				preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $path, $commands, PREG_SET_ORDER);
3639				$path_cmd = '';
3640				$this->subPathInit = true;
3641				$this->pathBBox = [999999, 999999, -999999, -999999];
3642
3643				foreach ($commands as $c) {
3644					if ((isset($c) && count($c) == 3) || (isset($c[2]) && $c[2] == '')) {
3645						list($tmp, $command, $arguments) = $c;
3646					} else {
3647						list($tmp, $command) = $c;
3648						$arguments = '';
3649					}
3650
3651					$path_cmd .= $this->svgPath($command, $arguments);
3652				}
3653
3654				if ($this->pathBBox[2] == -1999998) {
3655					$this->pathBBox[2] = 100;
3656				}
3657
3658				if ($this->pathBBox[3] == -1999998) {
3659					$this->pathBBox[3] = 100;
3660				}
3661
3662				if ($this->pathBBox[0] == 999999) {
3663					$this->pathBBox[0] = 0;
3664				}
3665
3666				if ($this->pathBBox[1] == 999999) {
3667					$this->pathBBox[1] = 0;
3668				}
3669
3670				$critere_style = $attribs;
3671				unset($critere_style['d']);
3672				$path_style = $this->svgDefineStyle($critere_style);
3673
3674				break;
3675
3676			case 'rect':
3677				if (!isset($attribs['x'])) {
3678					$attribs['x'] = 0;
3679				}
3680
3681				if (!isset($attribs['y'])) {
3682					$attribs['y'] = 0;
3683				}
3684
3685				if (!isset($attribs['rx'])) {
3686					$attribs['rx'] = 0;
3687				}
3688
3689				if (!isset($attribs['ry'])) {
3690					$attribs['ry'] = 0;
3691				}
3692
3693				$arguments = [];
3694
3695				if (isset($attribs['x'])) {
3696					$arguments['x'] = $attribs['x'];
3697				}
3698
3699				if (isset($attribs['y'])) {
3700					$arguments['y'] = $attribs['y'];
3701				}
3702
3703				if (isset($attribs['width'])) {
3704					$arguments['w'] = $attribs['width'];
3705				}
3706
3707				if (isset($attribs['height'])) {
3708					$arguments['h'] = $attribs['height'];
3709				}
3710
3711				if (isset($attribs['rx'])) {
3712					$arguments['rx'] = $attribs['rx'];
3713				}
3714
3715				if (isset($attribs['ry'])) {
3716					$arguments['ry'] = $attribs['ry'];
3717				}
3718
3719				$path_cmd = $this->svgRect($arguments);
3720				$critere_style = $attribs;
3721				unset($critere_style['x'], $critere_style['y'], $critere_style['rx'], $critere_style['ry'], $critere_style['height'], $critere_style['width']);
3722				$path_style = $this->svgDefineStyle($critere_style);
3723
3724				break;
3725
3726			case 'circle':
3727
3728				if (!isset($attribs['cx'])) {
3729					$attribs['cx'] = 0;
3730				}
3731
3732				if (!isset($attribs['cy'])) {
3733					$attribs['cy'] = 0;
3734				}
3735
3736				$arguments = [];
3737
3738				if (isset($attribs['cx'])) {
3739					$arguments['cx'] = $attribs['cx'];
3740				}
3741
3742				if (isset($attribs['cy'])) {
3743					$arguments['cy'] = $attribs['cy'];
3744				}
3745
3746				if (isset($attribs['r'])) {
3747					$arguments['rx'] = $attribs['r'];
3748				}
3749
3750				if (isset($attribs['r'])) {
3751					$arguments['ry'] = $attribs['r'];
3752				}
3753
3754				$path_cmd = $this->svgEllipse($arguments);
3755				$critere_style = $attribs;
3756				unset($critere_style['cx'], $critere_style['cy'], $critere_style['r']);
3757				$path_style = $this->svgDefineStyle($critere_style);
3758
3759				break;
3760
3761			case 'ellipse':
3762
3763				if (!isset($attribs['cx'])) {
3764					$attribs['cx'] = 0;
3765				}
3766
3767				if (!isset($attribs['cy'])) {
3768					$attribs['cy'] = 0;
3769				}
3770
3771				$arguments = [];
3772
3773				if (isset($attribs['cx'])) {
3774					$arguments['cx'] = $attribs['cx'];
3775				}
3776
3777				if (isset($attribs['cy'])) {
3778					$arguments['cy'] = $attribs['cy'];
3779				}
3780
3781				if (isset($attribs['rx'])) {
3782					$arguments['rx'] = $attribs['rx'];
3783				}
3784
3785				if (isset($attribs['ry'])) {
3786					$arguments['ry'] = $attribs['ry'];
3787				}
3788
3789				$path_cmd = $this->svgEllipse($arguments);
3790				$critere_style = $attribs;
3791				unset($critere_style['cx'], $critere_style['cy'], $critere_style['rx'], $critere_style['ry']);
3792				$path_style = $this->svgDefineStyle($critere_style);
3793
3794				break;
3795
3796			case 'line':
3797
3798				$arguments = [];
3799				$arguments[0] = (isset($attribs['x1']) ? $attribs['x1'] : '');
3800				$arguments[1] = (isset($attribs['y1']) ? $attribs['y1'] : '');
3801				$arguments[2] = (isset($attribs['x2']) ? $attribs['x2'] : '');
3802				$arguments[3] = (isset($attribs['y2']) ? $attribs['y2'] : '');
3803				$path_cmd = $this->svgPolyline($arguments, false);
3804				$critere_style = $attribs;
3805				unset($critere_style['x1'], $critere_style['y1'], $critere_style['x2'], $critere_style['y2']);
3806				$path_style = $this->svgDefineStyle($critere_style);
3807
3808				break;
3809
3810			case 'polyline':
3811
3812				$path = $attribs['points'];
3813				preg_match_all('/[0-9\-\.]*/', $path, $tmp, PREG_SET_ORDER);
3814				$arguments = [];
3815
3816				for ($i = 0; $i < count($tmp); $i++) {
3817					if ($tmp[$i][0] != '') {
3818						array_push($arguments, $tmp[$i][0]);
3819					}
3820				}
3821
3822				$path_cmd = $this->svgPolyline($arguments);
3823				$critere_style = $attribs;
3824				unset($critere_style['points']);
3825				$path_style = $this->svgDefineStyle($critere_style);
3826
3827				break;
3828
3829			case 'polygon':
3830
3831				$path = $attribs['points'];
3832				preg_match_all('/([\-]*[0-9\.]+)/', $path, $tmp);
3833				$arguments = [];
3834
3835				for ($i = 0; $i < count($tmp[0]); $i++) {
3836					if ($tmp[0][$i] != '') {
3837						array_push($arguments, $tmp[0][$i]);
3838					}
3839				}
3840
3841				$path_cmd = $this->svgPolygon($arguments);
3842				//	definition du style de la forme:
3843				$critere_style = $attribs;
3844				unset($critere_style['points']);
3845				$path_style = $this->svgDefineStyle($critere_style);
3846
3847				break;
3848
3849			// mPDF 5.7.4 Embedded image
3850			case 'image':
3851
3852				if (isset($attribs['xlink:href']) && $attribs['xlink:href']) {
3853					$this->svgImage($attribs);
3854				}
3855
3856				break;
3857
3858			case 'a':
3859
3860				if (isset($attribs['xlink:href'])) {
3861					unset($attribs['xlink:href']); // this should be a hyperlink
3862					// not handled like a xlink:href in other elements
3863				}
3864
3865				// fallthtough - then continue like a <g>
3866
3867			case 'g':
3868
3869				$array_style = $this->svgDefineStyle($attribs);
3870
3871				if (!empty($array_style['transformations'])) {
3872					// If in the middle of <text> element, add to textoutput, else WriteString
3873					if ($this->intext) {
3874						$this->textoutput .= ' q ' . $array_style['transformations'];
3875					} else { // mPDF 5.7.4
3876						$this->svgWriteString(' q ' . $array_style['transformations']);
3877					}
3878				}
3879
3880				array_push($this->svg_style, $array_style);
3881
3882				$this->svgDefineTxtStyle($attribs);
3883
3884				break;
3885
3886			case 'text':
3887
3888				$this->textlength = 0;  // mPDF 5.7.4
3889				$this->texttotallength = 0; // mPDF 5.7.4
3890				$this->textoutput = '';  // mPDF 5.7.4
3891				$this->textanchor = 'start'; // mPDF 5.7.4
3892				$this->textXorigin = 0;  // mPDF 5.7.4
3893				$this->textYorigin = 0;  // mPDF 5.7.4
3894
3895				$this->intext = true;   // mPDF 5.7.4
3896
3897				$styl = '';
3898
3899				if ($this->mpdf->svgClasses && isset($attribs['class']) && $attribs['class']) {
3900					$classes = preg_split('/\s+/', trim($attribs['class']));
3901					foreach ($classes as $class) {
3902						if (isset($this->cssManager->CSS['CLASS>>' . strtoupper($class)])) {
3903							$c = $this->cssManager->CSS['CLASS>>' . strtoupper($class)];
3904							foreach ($c as $prop => $val) {
3905								$styl .= strtolower($prop) . ':' . $val . ';';
3906							}
3907						}
3908					}
3909				}
3910
3911				if ($this->mpdf->svgAutoFont && isset($attribs['lang']) && $attribs['lang']) {
3912					if (!$this->mpdf->usingCoreFont) {
3913						if ($attribs['lang'] != $this->mpdf->default_lang) {
3914							list ($coreSuitable, $mpdf_unifont) = $this->languageToFont->getLanguageOptions($attribs['lang'], $this->mpdf->useAdobeCJK);
3915							if ($mpdf_unifont) {
3916								$styl .= 'font-family:' . $mpdf_unifont . ';';
3917							}
3918						}
3919					}
3920				}
3921
3922				if ($styl) {
3923					if (isset($attribs['style'])) {
3924						$attribs['style'] = $styl . $attribs['style'];
3925					} else {
3926						$attribs['style'] = $styl;
3927					}
3928				}
3929
3930				$array_style = $this->svgDefineStyle($attribs);
3931				if (!empty($array_style['transformations'])) {
3932					$this->textoutput .= ' q ' . $array_style['transformations']; // mPDF 5.7.4
3933				}
3934				array_push($this->svg_style, $array_style);
3935
3936				$this->txt_data = [];
3937				$x = isset($attribs['x']) ? $this->ConvertSVGSizePixels($attribs['x'], 'x') : 0;  // mPDF 5.7.4
3938				$y = isset($attribs['y']) ? $this->ConvertSVGSizePixels($attribs['y'], 'y') : 0;  // mPDF 5.7.4
3939				$x += isset($attribs['dx']) ? $this->ConvertSVGSizePixels($attribs['dx'], 'x') : 0;  // mPDF 5.7.4
3940				$y += isset($attribs['dy']) ? $this->ConvertSVGSizePixels($attribs['dy'], 'y') : 0;  // mPDF 5.7.4
3941
3942				$this->txt_data[0] = $x; // mPDF 5.7.4
3943				$this->txt_data[1] = $y; // mPDF 5.7.4
3944				$critere_style = $attribs;
3945				unset($critere_style['x'], $critere_style['y']);
3946				$this->svgDefineTxtStyle($critere_style);
3947
3948				$this->textanchor = $this->txt_style[count($this->txt_style) - 1]['text-anchor']; // mPDF 5.7.4
3949				$this->textXorigin = $this->txt_data[0];  // mPDF 5.7.4
3950				$this->textYorigin = $this->txt_data[1];  // mPDF 5.7.4
3951				$this->textjuststarted = true;  // mPDF 5.7.4
3952
3953				break;
3954
3955			// mPDF 5.7.4
3956			case 'tspan':
3957
3958				// OUTPUT CHUNK(s) UP To NOW (svgText updates $this->textlength)
3959				$p_cmd = $this->svgText();
3960				$this->textoutput .= $p_cmd;
3961				$tmp = count($this->svg_style) - 1;
3962				$current_style = $this->svg_style[$tmp];
3963
3964				$styl = '';
3965				if ($this->mpdf->svgClasses && isset($attribs['class']) && $attribs['class']) {
3966					$classes = preg_split('/\s+/', trim($attribs['class']));
3967					foreach ($classes as $class) {
3968						if (isset($this->cssManager->CSS['CLASS>>' . strtoupper($class)])) {
3969							$c = $this->cssManager->CSS['CLASS>>' . strtoupper($class)];
3970							foreach ($c as $prop => $val) {
3971								$styl .= strtolower($prop) . ':' . $val . ';';
3972							}
3973						}
3974					}
3975				}
3976
3977				if ($this->mpdf->svgAutoFont && isset($attribs['lang']) && $attribs['lang']) {
3978					if (!$this->mpdf->usingCoreFont) {
3979						if ($attribs['lang'] != $this->mpdf->default_lang) {
3980							list ($coreSuitable, $mpdf_unifont) = $this->languageToFont->getLanguageOptions($attribs['lang'], $this->mpdf->useAdobeCJK);
3981							if ($mpdf_unifont) {
3982								$styl .= 'font-family:' . $mpdf_unifont . ';';
3983							}
3984						}
3985					}
3986				}
3987
3988				if ($styl) {
3989					if (isset($attribs['style'])) {
3990						$attribs['style'] = $styl . $attribs['style'];
3991					} else {
3992						$attribs['style'] = $styl;
3993					}
3994				}
3995
3996				$array_style = $this->svgDefineStyle($attribs);
3997
3998				$this->txt_data = [];
3999
4000
4001				// If absolute position adjustment (x or y), creates new block of text for text-alignment
4002				if (isset($attribs['x']) || isset($attribs['y'])) {
4003					// If text-anchor middle|end, adjust
4004					if ($this->textanchor == 'end') {
4005						$tx = -$this->texttotallength;
4006					} elseif ($this->textanchor == 'middle') {
4007						$tx = -$this->texttotallength / 2;
4008					} else {
4009						$tx = 0;
4010					}
4011					while (preg_match('/mPDF-AXS\((.*?)\)/', $this->textoutput, $m)) {
4012						if ($tx) {
4013							$txk = $m[1] + ($tx * $this->kp);
4014							$this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', sprintf('%.4F', $txk), $this->textoutput, 1);
4015						} else {
4016							$this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', '\\1', $this->textoutput, 1);
4017						}
4018					}
4019
4020					$this->svgWriteString($this->textoutput);
4021
4022					$this->textXorigin += $this->textlength;
4023					$currentX = $this->textXorigin;
4024					$currentY = $this->textYorigin;
4025					$this->textlength = 0;
4026					$this->texttotallength = 0;
4027					$this->textoutput = '';
4028
4029					$x = isset($attribs['x']) ? $this->ConvertSVGSizePixels($attribs['x'], 'x') : $currentX;
4030					$y = isset($attribs['y']) ? $this->ConvertSVGSizePixels($attribs['y'], 'y') : $currentY;
4031
4032					$this->txt_data[0] = $x;
4033					$this->txt_data[1] = $y;
4034					$critere_style = $attribs;
4035					unset($critere_style['x'], $critere_style['y']);
4036					$this->svgDefineTxtStyle($critere_style);
4037
4038					$this->textanchor = $this->txt_style[count($this->txt_style) - 1]['text-anchor'];
4039					$this->textXorigin = $x;
4040					$this->textYorigin = $y;
4041				} else {
4042					$this->textXorigin += $this->textlength;
4043					$currentX = $this->textXorigin;
4044					$currentY = $this->textYorigin;
4045
4046					$currentX += isset($attribs['dx']) ? $this->ConvertSVGSizePixels($attribs['dx'], 'x') : 0;
4047					$currentY += isset($attribs['dy']) ? $this->ConvertSVGSizePixels($attribs['dy'], 'y') : 0;
4048
4049					$this->txt_data[0] = $currentX;
4050					$this->txt_data[1] = $currentY;
4051					$critere_style = $attribs;
4052					unset($critere_style['x'], $critere_style['y']);
4053					$this->svgDefineTxtStyle($critere_style);
4054					$this->textXorigin = $currentX;
4055					$this->textYorigin = $currentY;
4056				}
4057
4058				if (!empty($array_style['transformations'])) {
4059					$this->textoutput .= ' q ' . $array_style['transformations'];
4060				}
4061				array_push($this->svg_style, $array_style);
4062
4063				break;
4064		}
4065
4066		// insertion des path et du style dans le flux de donné general.
4067		if (isset($path_cmd) && $path_cmd) {
4068			// mPDF 5.0
4069			list($prestyle, $poststyle) = $this->svgStyle($path_style, $attribs, strtolower($name));
4070			if (isset($path_style['transformations']) && $path_style['transformations']) { // transformation on an element
4071				$this->svgWriteString(" q " . $path_style['transformations'] . $prestyle . $path_cmd . $poststyle . " Q\n");
4072			} else {
4073				$this->svgWriteString(" q " . $prestyle . $path_cmd . $poststyle . " Q\n"); // mPDF 5.7.4
4074			}
4075		}
4076	}
4077
4078	function characterData($parser, $data)
4079	{
4080		if ($this->inDefs) {
4081			return;
4082		}  // mPDF 5.7.2
4083
4084		if (isset($this->txt_data[2])) {
4085			$this->txt_data[2] .= $data;
4086		} else {
4087			$this->txt_data[2] = $data;
4088			$this->txt_data[0] = $this->textXorigin;
4089			$this->txt_data[1] = $this->textYorigin;
4090		}
4091	}
4092
4093	function xml_svg2pdf_end($parser, $name)
4094	{
4095		// mPDF 5.7.2
4096		// Don't output stuff inside <defs>
4097		if ($name == 'defs') {
4098			$this->inDefs = false;
4099			return;
4100		}
4101
4102		if ($this->inDefs) {
4103			return;
4104		}
4105
4106		switch ($name) {
4107			case "g":
4108			case "a":
4109				if ($this->intext) {
4110					$p_cmd = $this->svgText();
4111					$this->textoutput .= $p_cmd;
4112				}
4113
4114				$tmp = count($this->svg_style) - 1;
4115				$current_style = $this->svg_style[$tmp];
4116				if (!empty($current_style['transformations'])) {
4117					// If in the middle of <text> element, add to textoutput, else WriteString
4118					if ($this->intext) {
4119						$this->textoutput .= " Q\n";
4120					} // mPDF 5.7.4
4121					else {
4122						$this->svgWriteString(" Q\n");
4123					}
4124				}
4125
4126				array_pop($this->svg_style);
4127				array_pop($this->txt_style);
4128
4129				if ($this->intext) {
4130					$this->textXorigin += $this->textlength;
4131					$this->textlength = 0;
4132				}
4133
4134				break;
4135
4136			case 'font':
4137				$last_svg_fontdefw = '';
4138				break;
4139
4140			case 'font-face':
4141				$last_svg_fontid = '';
4142				$last_svg_fontstyle = '';
4143				break;
4144
4145			case 'radialgradient':
4146			case 'lineargradient':
4147				$last_gradid = '';
4148				break;
4149
4150			case 'text':
4151				if (!empty($this->txt_data[2])) {
4152					$this->txt_data[2] = rtrim($this->txt_data[2]); // mPDF 5.7.4
4153				}
4154
4155				$path_cmd = $this->svgText();
4156				$this->textoutput .= $path_cmd; // mPDF 5.7.4
4157				$tmp = count($this->svg_style) - 1;
4158				$current_style = $this->svg_style[$tmp];
4159
4160				if (!empty($current_style['transformations'])) {
4161					$this->textoutput .= " Q\n"; // mPDF 5.7.4
4162				}
4163				array_pop($this->svg_style);
4164				array_pop($this->txt_style); // mPDF 5.7.4
4165
4166				// mPDF 5.7.4
4167				// If text-anchor middle|end, adjust
4168				if ($this->textanchor == 'end') {
4169					$tx = -$this->texttotallength;
4170				} elseif ($this->textanchor == 'middle') {
4171					$tx = -$this->texttotallength / 2;
4172				} else {
4173					$tx = 0;
4174				}
4175				while (preg_match('/mPDF-AXS\((.*?)\)/', $this->textoutput, $m)) {
4176					if ($tx) {
4177						$txk = $m[1] + ($tx * $this->kp);
4178						$this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', sprintf('%.4F', $txk), $this->textoutput, 1);
4179					} else {
4180						$this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', '\\1', $this->textoutput, 1);
4181					}
4182				}
4183
4184				$this->svgWriteString($this->textoutput);
4185				$this->textlength = 0;
4186				$this->texttotallength = 0;
4187				$this->textoutput = '';
4188				$this->intext = false;   // mPDF 5.7.4
4189
4190				break;
4191			// mPDF 5.7.4
4192			case "tspan":
4193				$p_cmd = $this->svgText();
4194				$this->textoutput .= $p_cmd;
4195				$tmp = count($this->svg_style) - 1;
4196				$current_style = $this->svg_style[$tmp];
4197				if (!empty($current_style['transformations'])) {
4198					$this->textoutput .= " Q\n";
4199				}
4200				array_pop($this->svg_style);
4201				array_pop($this->txt_style);
4202
4203				$this->textXorigin += $this->textlength;
4204				$this->textlength = 0;
4205
4206				break;
4207		}
4208	}
4209
4210	private function computeBezierBoundingBox($start, $c)
4211	{
4212		$P0 = [$start[0], $start[1]];
4213		$P1 = [$c[0], $c[1]];
4214		$P2 = [$c[2], $c[3]];
4215		$P3 = [$c[4], $c[5]];
4216		$bounds = [];
4217		$bounds[0][] = $P0[0];
4218		$bounds[1][] = $P0[1];
4219		$bounds[0][] = $P3[0];
4220		$bounds[1][] = $P3[1];
4221
4222		for ($i = 0; $i <= 1; $i++) {
4223			$b = 6 * $P0[$i] - 12 * $P1[$i] + 6 * $P2[$i];
4224			$a = -3 * $P0[$i] + 9 * $P1[$i] - 9 * $P2[$i] + 3 * $P3[$i];
4225			$c = 3 * $P1[$i] - 3 * $P0[$i];
4226
4227			if ($a == 0) {
4228
4229				if ($b == 0) {
4230					continue;
4231				}
4232
4233				$t = -$c / $b;
4234
4235				if ($t > 0 && $t < 1) {
4236					$bounds[$i][] = (pow((1 - $t), 3) * $P0[$i] + 3 * pow((1 - $t), 2) * $t * $P1[$i] + 3 * (1 - $t) * pow($t, 2) * $P2[$i] + pow($t, 3) * $P3[$i]);
4237				}
4238
4239				continue;
4240			}
4241
4242			$b2ac = pow($b, 2) - 4 * $c * $a;
4243
4244			if ($b2ac < 0) {
4245				continue;
4246			}
4247
4248			$t1 = (-$b + sqrt($b2ac)) / (2 * $a);
4249
4250			if ($t1 > 0 && $t1 < 1) {
4251				$bounds[$i][] = (pow((1 - $t1), 3) * $P0[$i] + 3 * pow((1 - $t1), 2) * $t1 * $P1[$i] + 3 * (1 - $t1) * pow($t1, 2) * $P2[$i] + pow($t1, 3) * $P3[$i]);
4252			}
4253
4254			$t2 = (-$b - sqrt($b2ac)) / (2 * $a);
4255
4256			if ($t2 > 0 && $t2 < 1) {
4257				$bounds[$i][] = (pow((1 - $t2), 3) * $P0[$i] + 3 * pow((1 - $t2), 2) * $t2 * $P1[$i] + 3 * (1 - $t2) * pow($t2, 2) * $P2[$i] + pow($t2, 3) * $P3[$i]);
4258			}
4259		}
4260
4261		$x = min($bounds[0]);
4262		$x2 = max($bounds[0]);
4263		$y = min($bounds[1]);
4264		$y2 = max($bounds[1]);
4265
4266		return [$x, $y, $x2, $y2];
4267	}
4268
4269	private function testIntersectCircle($cx, $cy, $cr)
4270	{
4271		// Tests whether a circle fully encloses a rectangle 0,0,1,1
4272		// to see if any further radial gradients need adding (SVG)
4273		// If centre of circle is inside 0,0,1,1 square
4274		if ($cx >= 0 && $cx <= 1 && $cy >= 0 && $cy <= 1) {
4275			$maxd = 1.5;
4276		} else {  // distance to four corners
4277			$d1 = sqrt(pow(($cy - 0), 2) + pow(($cx - 0), 2));
4278			$d2 = sqrt(pow(($cy - 1), 2) + pow(($cx - 0), 2));
4279			$d3 = sqrt(pow(($cy - 0), 2) + pow(($cx - 1), 2));
4280			$d4 = sqrt(pow(($cy - 1), 2) + pow(($cx - 1), 2));
4281			$maxd = max($d1, $d2, $d3, $d4);
4282		}
4283
4284		return $cr < $maxd;
4285	}
4286
4287	private function testIntersect($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4)
4288	{
4289		// Tests whether line (x1, y1) and (x2, y2) [a gradient axis (perpendicular)]
4290		// intersects with a specific line segment (x3, y3) and (x4, y4)
4291		$a1 = $y2 - $y1;
4292		$b1 = $x1 - $x2;
4293		$c1 = $a1 * $x1 + $b1 * $y1;
4294		$a2 = $y4 - $y3;
4295		$b2 = $x3 - $x4;
4296		$c2 = $a2 * $x3 + $b2 * $y3;
4297		$det = $a1 * $b2 - $a2 * $b1;
4298
4299		if ($det == 0) { //Lines are parallel
4300			return false;
4301		} else {
4302			$x = ($b2 * $c1 - $b1 * $c2) / $det;
4303			$y = ($a1 * $c2 - $a2 * $c1) / $det;
4304			if ($x >= $x3 && $x <= $x4 && $y >= $y3 && $y <= $y4) {
4305				return true;
4306			}
4307		}
4308
4309		return false;
4310	}
4311
4312}
4313