1<?php
2
3namespace Mpdf;
4
5use Mpdf\Color\ColorConverter;
6use Mpdf\Css\TextVars;
7use Mpdf\File\StreamWrapperChecker;
8use Mpdf\Utils\Arrays;
9use Mpdf\Utils\UtfString;
10
11class CssManager
12{
13
14	/**
15	 * @var \Mpdf\Mpdf
16	 */
17	private $mpdf;
18
19	/**
20	 * @var \Mpdf\Cache
21	 */
22	private $cache;
23
24	/**
25	 * @var \Mpdf\SizeConverter
26	 */
27	private $sizeConverter;
28
29	/**
30	 * @var \Mpdf\Color\ColorConverter
31	 */
32	private $colorConverter;
33
34	var $tablecascadeCSS;
35
36	var $cascadeCSS;
37
38	var $CSS;
39
40	var $tbCSSlvl;
41
42	var $cell_border_dominance_B;
43
44	var $cell_border_dominance_L;
45
46	var $cell_border_dominance_R;
47
48	var $cell_border_dominance_T;
49
50	public function __construct(Mpdf $mpdf, Cache $cache, SizeConverter $sizeConverter, ColorConverter $colorConverter)
51	{
52		$this->mpdf = $mpdf;
53		$this->cache = $cache;
54		$this->sizeConverter = $sizeConverter;
55
56		$this->tablecascadeCSS = [];
57		$this->CSS = [];
58		$this->cascadeCSS = [];
59		$this->tbCSSlvl = 0;
60		$this->colorConverter = $colorConverter;
61	}
62
63	function ReadCSS($html)
64	{
65		preg_match_all('/<style[^>]*media=["\']([^"\'>]*)["\'].*?<\/style>/is', $html, $m);
66		$count_m = count($m[0]);
67		for ($i = 0; $i < $count_m; $i++) {
68			if ($this->mpdf->CSSselectMedia && !preg_match('/(' . trim($this->mpdf->CSSselectMedia) . '|all)/i', $m[1][$i])) {
69				$html = str_replace($m[0][$i], '', $html);
70			}
71		}
72
73		preg_match_all('/<link[^>]*media=["\']([^"\'>]*)["\'].*?>/is', $html, $m);
74		$count_m = count($m[0]);
75		for ($i = 0; $i < $count_m; $i++) {
76			if ($this->mpdf->CSSselectMedia && !preg_match('/(' . trim($this->mpdf->CSSselectMedia) . '|all)/i', $m[1][$i])) {
77				$html = str_replace($m[0][$i], '', $html);
78			}
79		}
80
81		// mPDF 5.5.02
82		// Remove Comment tags <!-- ... --> inside CSS as <style> in HTML document
83		// Remove Comment tags /* ...  */ inside CSS as <style> in HTML document
84		// But first, we replace upper and mixed case closing style tag with lower
85		// case so we can use str_replace later.
86		preg_match_all('/<style.*?>(.*?)<\/style>/si', $html, $m);
87		$count_m = count($m[1]);
88		if ($count_m) {
89			for ($i = 0; $i < $count_m; $i++) {
90				// Remove comment tags
91				$sub = preg_replace('/(<\!\-\-|\-\->)/s', ' ', $m[1][$i]);
92				$sub = '>'.preg_replace('|/\*.*?\*/|s', ' ', $sub).'</style>';
93				$html = str_replace('>'.$m[1][$i].'</style>', $sub, $html);
94			}
95		}
96
97		$html = preg_replace('/<!--mpdf/i', '', $html);
98		$html = preg_replace('/mpdf-->/i', '', $html);
99		$html = preg_replace('/<\!\-\-.*?\-\->/s', ' ', $html);
100
101		$match = 0; // no match for instance
102		$CSSext = [];
103
104		// CSS inside external files
105		$regexp = '/<link[^>]*rel=["\']stylesheet["\'][^>]*href=["\']([^>"\']*)["\'].*?>/si';
106		$x = preg_match_all($regexp, $html, $cxt);
107		if ($x) {
108			$match += $x;
109			$CSSext = $cxt[1];
110		}
111		$regexp = '/<link[^>]*href=["\']([^>"\']*)["\'][^>]*?rel=["\']stylesheet["\'].*?>/si';
112		$x = preg_match_all($regexp, $html, $cxt);
113		if ($x) {
114			$match += $x;
115			$CSSext = array_merge($CSSext, $cxt[1]);
116		}
117
118		// look for @import stylesheets
119		// $regexp = '/@import url\([\'\"]{0,1}([^\)]*?\.css)[\'\"]{0,1}\)/si';
120		// $regexp = '/@import url\([\'\"]{0,1}([^\)]*?\.css(\?\S+)?)[\'\"]{0,1}\)/si';
121		$regexp = '/@import url\([\'\"]{0,1}(\S*?\.css(\?[^\s\'\"]+)?)[\'\"]{0,1}\)\;?/si';
122		$x = preg_match_all($regexp, $html, $cxt);
123		if ($x) {
124			$match += $x;
125			$CSSext = array_merge($CSSext, $cxt[1]);
126		}
127
128		// look for @import without the url()
129		// $regexp = '/@import [\'\"]{0,1}([^;]*?\.css)[\'\"]{0,1}/si';
130		// $regexp = '/@import [\'\"]{0,1}([^;]*?\.css(\?\S+)?)[\'\"]{0,1}/si';
131		$regexp = '/@import (?!url)[\'\"]{0,1}(\S*?\.css(\?[^\s\'\"]+)?)[\'\"]{0,1}\;?/si';
132		$x = preg_match_all($regexp, $html, $cxt);
133		if ($x) {
134			$match += $x;
135			$CSSext = array_merge($CSSext, $cxt[1]);
136		}
137
138		$ind = 0;
139		$CSSstr = '';
140
141		if (!is_array($this->cascadeCSS)) {
142			$this->cascadeCSS = [];
143		}
144
145		while ($match) {
146
147			$path = $CSSext[$ind];
148
149			$path = htmlspecialchars_decode($path); // mPDF 6
150
151			$this->mpdf->GetFullPath($path);
152
153			$CSSextblock = $this->getFileContents($path);
154			if ($CSSextblock) {
155				// look for embedded @import stylesheets in other stylesheets
156				// and fix url paths (including background-images) relative to stylesheet
157				// $regexpem = '/@import url\([\'\"]{0,1}(.*?\.css)[\'\"]{0,1}\)/si';
158				$regexpem = '/@import url\([\'\"]{0,1}(.*?\.css(\?\S+)?)[\'\"]{0,1}\)/si';
159				$xem = preg_match_all($regexpem, $CSSextblock, $cxtem);
160				$cssBasePath = preg_replace('/\/[^\/]*$/', '', $path) . '/';
161				if ($xem) {
162					foreach ($cxtem[1] as $cxtembedded) {
163						// path is relative to original stylesheet!!
164						$this->mpdf->GetFullPath($cxtembedded, $cssBasePath);
165						$match++;
166						$CSSext[] = $cxtembedded;
167					}
168				}
169				$regexpem = '/(background[^;]*url\s*\(\s*[\'\"]{0,1})([^\)\'\"]*)([\'\"]{0,1}\s*\))/si';
170				$xem = preg_match_all($regexpem, $CSSextblock, $cxtem);
171				if ($xem) {
172					$count_cxtem = count($cxtem[0]);
173					for ($i = 0; $i < $count_cxtem; $i++) {
174						// path is relative to original stylesheet!!
175						$embedded = $cxtem[2][$i];
176						if (!preg_match('/^data:image/i', $embedded)) { // mPDF 5.5.13
177							$this->mpdf->GetFullPath($embedded, $cssBasePath);
178							$CSSextblock = str_replace($cxtem[0][$i], ($cxtem[1][$i] . $embedded . $cxtem[3][$i]), $CSSextblock);
179						}
180					}
181				}
182				$CSSstr .= ' ' . $CSSextblock;
183			}
184			$match--;
185			$ind++;
186		}
187
188		// CSS as <style> in HTML document
189		$regexp = '/<style.*?>(.*?)<\/style>/si';
190		$match = preg_match_all($regexp, $html, $CSSblock);
191		if ($match) {
192			$tmpCSSstr = implode(' ', $CSSblock[1]);
193			$regexpem = '/(background[^;]*url\s*\(\s*[\'\"]{0,1})([^\)\'\"]*)([\'\"]{0,1}\s*\))/si';
194			$xem = preg_match_all($regexpem, $tmpCSSstr, $cxtem);
195			if ($xem) {
196				$count_cxtem = count($cxtem[0]);
197				for ($i = 0; $i < $count_cxtem; $i++) {
198					$embedded = $cxtem[2][$i];
199					if (!preg_match('/^data:image/i', $embedded)) { // mPDF 5.5.13
200						$this->mpdf->GetFullPath($embedded);
201						$tmpCSSstr = str_replace($cxtem[0][$i], ($cxtem[1][$i] . $embedded . $cxtem[3][$i]), $tmpCSSstr);
202					}
203				}
204			}
205			$CSSstr .= ' ' . $tmpCSSstr;
206		}
207
208		// Remove comments
209		$CSSstr = preg_replace('|/\*.*?\*/|s', ' ', $CSSstr);
210		$CSSstr = preg_replace('/[\s\n\r\t\f]/s', ' ', $CSSstr);
211
212		if (preg_match('/@media/', $CSSstr)) {
213			preg_match_all('/@media(.*?)\{(([^\{\}]*\{[^\{\}]*\})+)\s*\}/is', $CSSstr, $m);
214			$count_m = count($m[0]);
215			for ($i = 0; $i < $count_m; $i++) {
216				if ($this->mpdf->CSSselectMedia && !preg_match('/(' . trim($this->mpdf->CSSselectMedia) . '|all)/i', $m[1][$i])) {
217					$CSSstr = str_replace($m[0][$i], '', $CSSstr);
218				} else {
219					$CSSstr = str_replace($m[0][$i], ' ' . $m[2][$i] . ' ', $CSSstr);
220				}
221			}
222		}
223
224		// Replace any background: url(data:image... with temporary image file reference
225		preg_match_all("/(url\(data:image\/(jpeg|gif|png);base64,(.*?)\))/si", $CSSstr, $idata); // mPDF 5.7.2
226		$count_idata = count($idata[0]);
227		if ($count_idata) {
228			for ($i = 0; $i < $count_idata; $i++) {
229				$file = $this->cache->write('_tempCSSidata' . random_int(1, 10000) . '_' . $i . '.' . $idata[2][$i], base64_decode($idata[3][$i]));
230				$CSSstr = str_replace($idata[0][$i], 'url("' . $file . '")', $CSSstr);  // mPDF 5.5.17
231			}
232		}
233
234		$CSSstr = preg_replace('/(<\!\-\-|\-\->)/s', ' ', $CSSstr);
235
236		// mPDF 5.7.4 URLs
237		// Characters "(" ")" and ";" in url() e.g. background-image, cause problems parsing the CSS string
238		// URLencode ( and ), but change ";" to a code which can be converted back after parsing (so as not to confuse ;
239		// with a segment delimiter in the URI)
240		$tempmarker = '%ZZ';
241		if (strpos($CSSstr, 'url(') !== false) {
242			preg_match_all('/url\(\"(.*?)\"\)/', $CSSstr, $m);
243			$count_m = count($m[1]);
244			for ($i = 0; $i < $count_m; $i++) {
245				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
246				$CSSstr = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $CSSstr);
247			}
248			preg_match_all('/url\(\'(.*?)\'\)/', $CSSstr, $m);
249			$count_m = count($m[1]);
250			for ($i = 0; $i < $count_m; $i++) {
251				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
252				$CSSstr = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $CSSstr);
253			}
254			preg_match_all('/url\(([^\'\"].*?[^\'\"])\)/', $CSSstr, $m);
255			$count_m = count($m[1]);
256			for ($i = 0; $i < $count_m; $i++) {
257				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
258				$CSSstr = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $CSSstr);
259			}
260		}
261
262		if ($CSSstr) {
263
264			$classproperties = []; // mPDF 6
265			preg_match_all('/(.*?)\{(.*?)\}/', $CSSstr, $styles);
266			$styles_count = count($styles[1]);
267			for ($i = 0; $i < $styles_count; $i++) {
268
269				// SET array e.g. $classproperties['COLOR'] = '#ffffff';
270				$stylestr = trim($styles[2][$i]);
271				$stylearr = explode(';', $stylestr);
272
273				foreach ($stylearr as $sta) {
274					if (trim($sta)) {
275						// Changed to allow style="background: url('http://www.bpm1.com/bg.jpg')"
276						$tmp = explode(':', $sta, 2);
277						$property = $tmp[0];
278						if (isset($tmp[1])) {
279							$value = $tmp[1];
280						} else {
281							$value = '';
282						}
283						$value = str_replace($tempmarker, ';', $value); // mPDF 5.7.4 URLs
284						$property = trim($property);
285						$value = preg_replace('/\s*!important/i', '', $value);
286						$value = trim($value);
287						if ($property && ($value || $value === '0')) {
288							// Ignores -webkit-gradient so doesn't override -moz-
289							if ((strtoupper($property) === 'BACKGROUND-IMAGE' || strtoupper($property) === 'BACKGROUND') && false !== stripos($value, '-webkit-gradient')) {
290								continue;
291							}
292							$classproperties[strtoupper($property)] = $value;
293						}
294					}
295				}
296
297				$classproperties = $this->fixCSS($classproperties);
298				$tagstr = strtoupper(trim($styles[1][$i]));
299				$tagarr = explode(',', $tagstr);
300				$pageselectors = false; // used to turn on $this->mpdf->mirrorMargins
301
302				foreach ($tagarr as $tg) {
303
304					if (preg_match('/NTH-CHILD\((\s*(([\-+]?\d*)N(\s*[\-+]\s*\d+)?|[\-+]?\d+|ODD|EVEN)\s*)\)/', $tg, $m)) {
305						$tg = preg_replace('/NTH-CHILD\(.*\)/', 'NTH-CHILD(' . str_replace(' ', '', $m[1]) . ')', $tg);
306					}
307
308					$tags = preg_split('/\s+/', trim($tg));
309					$level = count($tags);
310					$t = '';
311					$t2 = '';
312					$t3 = '';
313
314					if (trim($tags[0]) === '@PAGE') {
315
316						if (isset($tags[0])) {
317							$t = trim($tags[0]);
318						}
319
320						if (isset($tags[1])) {
321							$t2 = trim($tags[1]);
322						}
323
324						if (isset($tags[2])) {
325							$t3 = trim($tags[2]);
326						}
327
328						$tag = '';
329						if ($level === 1) {
330							$tag = $t;
331						} elseif ($level === 2 && preg_match('/^[:](.*)$/', $t2, $m)) {
332							$tag = $t . '>>PSEUDO>>' . $m[1];
333							if ($m[1] === 'LEFT' || $m[1] === 'RIGHT') {
334								$pageselectors = true;
335							} // used to turn on $this->mpdf->mirrorMargins
336						} elseif ($level === 2) {
337							$tag = $t . '>>NAMED>>' . $t2;
338						} elseif ($level === 3 && preg_match('/^[:](.*)$/', $t3, $m)) {
339							$tag = $t . '>>NAMED>>' . $t2 . '>>PSEUDO>>' . $m[1];
340							if ($m[1] === 'LEFT' || $m[1] === 'RIGHT') {
341								$pageselectors = true;
342							} // used to turn on $this->mpdf->mirrorMargins
343						}
344
345						if (isset($this->CSS[$tag]) && $tag) {
346							$this->CSS[$tag] = $this->array_merge_recursive_unique($this->CSS[$tag], $classproperties);
347						} elseif ($tag) {
348							$this->CSS[$tag] = $classproperties;
349						}
350
351					} elseif ($level === 1) {  // e.g. p or .class or #id or p.class or p#id
352
353						if (isset($tags[0])) {
354							$t = trim($tags[0]);
355						}
356
357						if ($t) {
358
359							$tag = '';
360
361							if (preg_match('/^[.](.*)$/', $t, $m)) {
362								$classes = explode('.', $m[1]);
363								sort($classes);
364								$tag = 'CLASS>>' . join('.', $classes);
365							} elseif (preg_match('/^[#](.*)$/', $t, $m)) {
366								$tag = 'ID>>' . $m[1];
367							} elseif (preg_match('/^\[LANG=[\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\]$/', $t, $m)) {
368								$tag = 'LANG>>' . strtolower($m[1]);
369							} elseif (preg_match('/^:LANG\([\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\)$/', $t, $m)) { // mPDF 6  Special case for lang as attribute selector
370								$tag = 'LANG>>' . strtolower($m[1]);
371							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')[.](.*)$/', $t, $m)) { // mPDF 6  Special case for lang as attribute selector
372								$classes = explode('.', $m[2]);
373								sort($classes);
374								$tag = $m[1] . '>>CLASS>>' . join('.', $classes);
375							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')\s*:NTH-CHILD\((.*)\)$/', $t, $m)) {
376								$tag = $m[1] . '>>SELECTORNTHCHILD>>' . $m[2];
377							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')[#](.*)$/', $t, $m)) {
378								$tag = $m[1] . '>>ID>>' . $m[2];
379							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')\[LANG=[\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\]$/', $t, $m)) {
380								$tag = $m[1] . '>>LANG>>' . strtolower($m[2]);
381							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . '):LANG\([\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\)$/', $t, $m)) {  // mPDF 6  Special case for lang as attribute selector
382								$tag = $m[1] . '>>LANG>>' . strtolower($m[2]);
383							} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')$/', $t)) { // mPDF 6  Special case for lang as attribute selector
384								$tag = $t;
385							}
386
387							if (isset($this->CSS[$tag]) && $tag) {
388								$this->CSS[$tag] = $this->array_merge_recursive_unique($this->CSS[$tag], $classproperties);
389							} elseif ($tag) {
390								$this->CSS[$tag] = $classproperties;
391							}
392						}
393
394					} else {
395
396						$tmp = [];
397
398						for ($n = 0; $n < $level; $n++) {
399
400							$tag = '';
401
402							if (isset($tags[$n])) {
403								$t = trim($tags[$n]);
404							} else {
405								$t = '';
406							}
407
408							if ($t) {
409
410								if (preg_match('/^[.](.*)$/', $t, $m)) {
411									$classes = explode('.', $m[1]);
412									sort($classes);
413									$tag = 'CLASS>>' . join('.', $classes);
414								} elseif (preg_match('/^[#](.*)$/', $t, $m)) {
415									$tag = 'ID>>' . $m[1];
416								} elseif (preg_match('/^\[LANG=[\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\]$/', $t, $m)) {
417									$tag = 'LANG>>' . strtolower($m[1]);
418								} elseif (preg_match('/^:LANG\([\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\)$/', $t, $m)) { // mPDF 6  Special case for lang as attribute selector
419									$tag = 'LANG>>' . strtolower($m[1]);
420								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')[.](.*)$/', $t, $m)) { // mPDF 6  Special case for lang as attribute selector
421									$classes = explode('.', $m[2]);
422									sort($classes);
423									$tag = $m[1] . '>>CLASS>>' . join('.', $classes);
424								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')\s*:NTH-CHILD\((.*)\)$/', $t, $m)) {
425									$tag = $m[1] . '>>SELECTORNTHCHILD>>' . $m[2];
426								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')[#](.*)$/', $t, $m)) {
427									$tag = $m[1] . '>>ID>>' . $m[2];
428								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')\[LANG=[\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\]$/', $t, $m)) {
429									$tag = $m[1] . '>>LANG>>' . strtolower($m[2]);
430								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . '):LANG\([\'\"]{0,1}([A-Z\-]{2,11})[\'\"]{0,1}\)$/', $t, $m)) { // mPDF 6  Special case for lang as attribute selector
431									$tag = $m[1] . '>>LANG>>' . strtolower($m[2]);
432								} elseif (preg_match('/^(' . $this->mpdf->allowedCSStags . ')$/', $t)) { // mPDF 6  Special case for lang as attribute selector
433									$tag = $t;
434								}
435
436								if ($tag) {
437									$tmp[] = $tag;
438								} else {
439									break;
440								}
441							}
442						}
443
444						if ($tag) {
445							$x = &$this->cascadeCSS;
446							foreach ($tmp as $tp) {
447								$x = &$x[$tp];
448							}
449							$x = $this->array_merge_recursive_unique($x, $classproperties);
450							$x['depth'] = $level;
451						}
452					}
453				}
454				if ($pageselectors) {
455					$this->mpdf->mirrorMargins = true;
456				}
457				$classproperties = [];
458			}
459		}
460
461		// Remove CSS (tags and content), if any
462		$regexp = '/<style.*?>(.*?)<\/style>/si'; // it can be <style> or <style type="txt/css">
463		$html = preg_replace($regexp, '', $html);
464
465		return $html;
466	}
467
468	function readInlineCSS($html)
469	{
470		$html = htmlspecialchars_decode($html); // mPDF 5.7.4 URLs
471		// mPDF 5.7.4 URLs
472		// Characters "(" ")" and ";" in url() e.g. background-image, cause problems parsing the CSS string
473		// URLencode ( and ), but change ";" to a code which can be converted back after parsing (so as not to confuse ;
474		// with a segment delimiter in the URI)
475		$tempmarker = '%ZZ';
476
477		if (strpos($html, 'url(') !== false) {
478			preg_match_all('/url\(\"(.*?)\"\)/', $html, $m);
479			$m_count = count($m[1]);
480			for ($i = 0; $i < $m_count; $i++) {
481				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
482				$html = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $html);
483			}
484			preg_match_all('/url\(\'(.*?)\'\)/', $html, $m);
485			$m_count = count($m[1]);
486			for ($i = 0; $i < $m_count; $i++) {
487				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
488				$html = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $html);
489			}
490			preg_match_all('/url\(([^\'\"].*?[^\'\"])\)/', $html, $m);
491			$m_count = count($m[1]);
492			for ($i = 0; $i < $m_count; $i++) {
493				$tmp = str_replace(['(', ')', ';'], ['%28', '%29', $tempmarker], $m[1][$i]);
494				$html = str_replace($m[0][$i], 'url(\'' . $tmp . '\')', $html);
495			}
496		}
497
498		// Fix incomplete CSS code
499		$size = strlen($html) - 1;
500		if (substr($html, $size, 1) !== ';') {
501			$html .= ';';
502		}
503
504		// Make CSS[Name-of-the-class] = array(key => value)
505		$regexp = '|\\s*?(\\S+?):(.+?);|i';
506		preg_match_all($regexp, $html, $styleinfo);
507		$properties = $styleinfo[1];
508		$values = $styleinfo[2];
509
510		// Array-properties and Array-values must have the SAME SIZE!
511		$classproperties = [];
512		$properties_count = count($properties);
513		for ($i = 0; $i < $properties_count; $i++) {
514
515			// Ignores -webkit-gradient so doesn't override -moz-
516			if ((strtoupper($properties[$i]) === 'BACKGROUND-IMAGE' || strtoupper($properties[$i]) === 'BACKGROUND') && false !== stripos($values[$i], '-webkit-gradient')) {
517				continue;
518			}
519
520			$values[$i] = str_replace($tempmarker, ';', $values[$i]); // mPDF 5.7.4 URLs
521			$classproperties[strtoupper($properties[$i])] = trim($values[$i]);
522		}
523
524		return $this->fixCSS($classproperties);
525	}
526
527	function _fix_borderStr($bd)
528	{
529		preg_match_all("/\((.*?)\)/", $bd, $m);
530		if (count($m[1])) {
531			$m_count = count($m[1]);
532			for ($i = 0; $i < $m_count; $i++) {
533				$sub = str_replace(' ', '', $m[1][$i]);
534				$bd = str_replace($m[1][$i], $sub, $bd);
535			}
536		}
537
538		$prop = preg_split('/\s+/', trim($bd));
539		$w = 'medium';
540		$c = '#000000';
541		$s = 'none';
542
543		$prop_count = count($prop);
544		if ($prop_count === 1) {
545
546			// solid
547			if (in_array($prop[0], $this->mpdf->borderstyles) || $prop[0] === 'none' || $prop[0] === 'hidden') {
548				$s = $prop[0];
549			} // #000000
550			elseif (is_array($this->colorConverter->convert($prop[0], $this->mpdf->PDFAXwarnings))) {
551				$c = $prop[0];
552			} // 1px
553			else {
554				$w = $prop[0];
555			}
556
557		} elseif ($prop_count === 2) {
558			// 1px solid
559			if (in_array($prop[1], $this->mpdf->borderstyles) || $prop[1] === 'none' || $prop[1] === 'hidden') {
560				$w = $prop[0];
561				$s = $prop[1];
562			} // solid #000000
563			elseif (in_array($prop[0], $this->mpdf->borderstyles) || $prop[0] === 'none' || $prop[0] === 'hidden') {
564				$s = $prop[0];
565				$c = $prop[1];
566			} // 1px #000000
567			else {
568				$w = $prop[0];
569				$c = $prop[1];
570			}
571
572		} elseif ($prop_count === 3) {
573			// Change #000000 1px solid to 1px solid #000000 (proper)
574			if (0 === strpos($prop[0], '#')) {
575				$c = $prop[0];
576				$w = $prop[1];
577				$s = $prop[2];
578			} // Change solid #000000 1px to 1px solid #000000 (proper)
579			elseif (substr($prop[0], 1, 1) === '#') {
580				$s = $prop[0];
581				$c = $prop[1];
582				$w = $prop[2];
583			} // Change solid 1px #000000 to 1px solid #000000 (proper)
584			elseif (in_array($prop[0], $this->mpdf->borderstyles) || $prop[0] === 'none' || $prop[0] === 'hidden') {
585				$s = $prop[0];
586				$w = $prop[1];
587				$c = $prop[2];
588			} else {
589				$w = $prop[0];
590				$s = $prop[1];
591				$c = $prop[2];
592			}
593
594		} else {
595			return '';
596		}
597
598		$s = strtolower($s);
599
600		return $w . ' ' . $s . ' ' . $c;
601	}
602
603	function fixCSS($prop)
604	{
605		if (!is_array($prop) || (count($prop) == 0)) {
606			return [];
607		}
608
609		$newprop = [];
610
611		foreach ($prop as $k => $v) {
612
613			if ($k !== 'BACKGROUND-IMAGE' && $k !== 'BACKGROUND' && $k !== 'ODD-HEADER-NAME' && $k !== 'EVEN-HEADER-NAME' && $k !== 'ODD-FOOTER-NAME' && $k !== 'EVEN-FOOTER-NAME' && $k !== 'HEADER' && $k !== 'FOOTER') {
614				$v = strtolower($v);
615			}
616
617			if ($k === 'FONT') {
618
619				$s = trim($v);
620
621				preg_match_all('/\"(.*?)\"/', $s, $ff);
622				if (count($ff[1])) {
623					foreach ($ff[1] as $ffp) {
624						$w = preg_split('/\s+/', $ffp);
625						$s = preg_replace('/\"' . $ffp . '\"/', $w[0], $s);
626					}
627				}
628
629				preg_match_all('/\'(.*?)\'/', $s, $ff);
630				if (count($ff[1])) {
631					foreach ($ff[1] as $ffp) {
632						$w = preg_split('/\s+/', $ffp);
633						$s = preg_replace('/\'' . $ffp . '\'/', $w[0], $s);
634					}
635				}
636
637				$s = preg_replace('/\s*,\s*/', ',', $s);
638				$bits = preg_split('/\s+/', $s);
639				if (count($bits) > 1) {
640					$k = 'FONT-FAMILY';
641					$v = $bits[(count($bits) - 1)];
642					$fs = $bits[(count($bits) - 2)];
643
644					if (preg_match('/(.*?)\/(.*)/', $fs, $fsp)) {
645						$newprop['FONT-SIZE'] = $fsp[1];
646						$newprop['LINE-HEIGHT'] = $fsp[2];
647					} else {
648						$newprop['FONT-SIZE'] = $fs;
649					}
650
651					if (preg_match('/(italic|oblique)/i', $s)) {
652						$newprop['FONT-STYLE'] = 'italic';
653					} else {
654						$newprop['FONT-STYLE'] = 'normal';
655					}
656
657					if (false !== stripos($s, 'bold')) {
658						$newprop['FONT-WEIGHT'] = 'bold';
659					} else {
660						$newprop['FONT-WEIGHT'] = 'normal';
661					}
662
663					if (false !== stripos($s, 'small-caps')) {
664						$newprop['TEXT-TRANSFORM'] = 'uppercase';
665					}
666				}
667
668			} elseif ($k === 'FONT-FAMILY') {
669
670				$aux_fontlist = explode(',', $v);
671				$found = 0;
672
673				foreach ($aux_fontlist as $f) {
674
675					$fonttype = trim($f);
676					$fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
677					$fonttype = preg_replace('/ /', '', $fonttype);
678					$v = strtolower(trim($fonttype));
679
680					if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) {
681						$v = $this->mpdf->fonttrans[$v];
682					}
683
684					if ((!$this->mpdf->onlyCoreFonts && in_array($v, $this->mpdf->available_unifonts)) ||
685						in_array($v, ['ccourier', 'ctimes', 'chelvetica']) ||
686						($this->mpdf->onlyCoreFonts && in_array($v, ['courier', 'times', 'helvetica', 'arial'])) ||
687						in_array($v, ['sjis', 'uhc', 'big5', 'gb'])) {
688						$newprop[$k] = $v;
689						$found = 1;
690						break;
691					}
692				}
693
694				if (!$found) {
695					foreach ($aux_fontlist as $f) {
696
697						$fonttype = trim($f);
698						$fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype);
699						$fonttype = preg_replace('/ /', '', $fonttype);
700						$v = strtolower(trim($fonttype));
701
702						if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) {
703							$v = $this->mpdf->fonttrans[$v];
704						}
705
706						if (in_array($v, $this->mpdf->sans_fonts) || in_array($v, $this->mpdf->serif_fonts) || in_array($v, $this->mpdf->mono_fonts)) {
707							$newprop[$k] = $v;
708							break;
709						}
710					}
711				}
712
713			} elseif ($k === 'FONT-VARIANT') {
714
715				if (preg_match('/(normal|none)/', $v, $m)) {
716					$newprop['FONT-VARIANT-LIGATURES'] = $m[1];
717					$newprop['FONT-VARIANT-CAPS'] = $m[1];
718					$newprop['FONT-VARIANT-NUMERIC'] = $m[1];
719					$newprop['FONT-VARIANT-ALTERNATES'] = $m[1];
720				} else {
721					if (preg_match_all('/(no-common-ligatures|\bcommon-ligatures|no-discretionary-ligatures|\bdiscretionary-ligatures|no-historical-ligatures|\bhistorical-ligatures|no-contextual|\bcontextual)/i', $v, $m)) {
722						$newprop['FONT-VARIANT-LIGATURES'] = implode(' ', $m[1]);
723					}
724					if (preg_match('/(all-small-caps|\bsmall-caps|all-petite-caps|\bpetite-caps|unicase|titling-caps)/i', $v, $m)) {
725						$newprop['FONT-VARIANT-CAPS'] = $m[1];
726					}
727					if (preg_match_all('/(lining-nums|oldstyle-nums|proportional-nums|tabular-nums|diagonal-fractions|stacked-fractions)/i', $v, $m)) {
728						$newprop['FONT-VARIANT-NUMERIC'] = implode(' ', $m[1]);
729					}
730					if (preg_match('/(historical-forms)/i', $v, $m)) {
731						$newprop['FONT-VARIANT-ALTERNATES'] = $m[1];
732					}
733				}
734
735			} elseif ($k === 'MARGIN') {
736
737				$tmp = $this->expand24($v);
738
739				$newprop['MARGIN-TOP'] = $tmp['T'];
740				$newprop['MARGIN-RIGHT'] = $tmp['R'];
741				$newprop['MARGIN-BOTTOM'] = $tmp['B'];
742				$newprop['MARGIN-LEFT'] = $tmp['L'];
743
744			} elseif ($k === 'BORDER-RADIUS' || $k === 'BORDER-TOP-LEFT-RADIUS' || $k === 'BORDER-TOP-RIGHT-RADIUS' || $k === 'BORDER-BOTTOM-LEFT-RADIUS' || $k === 'BORDER-BOTTOM-RIGHT-RADIUS') {
745
746				$tmp = $this->border_radius_expand($v, $k);
747
748				if (isset($tmp['TL-H'])) {
749					$newprop['BORDER-TOP-LEFT-RADIUS-H'] = $tmp['TL-H'];
750				}
751				if (isset($tmp['TL-V'])) {
752					$newprop['BORDER-TOP-LEFT-RADIUS-V'] = $tmp['TL-V'];
753				}
754				if (isset($tmp['TR-H'])) {
755					$newprop['BORDER-TOP-RIGHT-RADIUS-H'] = $tmp['TR-H'];
756				}
757				if (isset($tmp['TR-V'])) {
758					$newprop['BORDER-TOP-RIGHT-RADIUS-V'] = $tmp['TR-V'];
759				}
760				if (isset($tmp['BL-H'])) {
761					$newprop['BORDER-BOTTOM-LEFT-RADIUS-H'] = $tmp['BL-H'];
762				}
763				if (isset($tmp['BL-V'])) {
764					$newprop['BORDER-BOTTOM-LEFT-RADIUS-V'] = $tmp['BL-V'];
765				}
766				if (isset($tmp['BR-H'])) {
767					$newprop['BORDER-BOTTOM-RIGHT-RADIUS-H'] = $tmp['BR-H'];
768				}
769				if (isset($tmp['BR-V'])) {
770					$newprop['BORDER-BOTTOM-RIGHT-RADIUS-V'] = $tmp['BR-V'];
771				}
772
773			} elseif ($k === 'PADDING') {
774
775				$tmp = $this->expand24($v);
776
777				$newprop['PADDING-TOP'] = $tmp['T'];
778				$newprop['PADDING-RIGHT'] = $tmp['R'];
779				$newprop['PADDING-BOTTOM'] = $tmp['B'];
780				$newprop['PADDING-LEFT'] = $tmp['L'];
781
782			} elseif ($k === 'BORDER') {
783
784				if ($v == '1') {
785					$v = '1px solid #000000';
786				} else {
787					$v = $this->_fix_borderStr($v);
788				}
789
790				$newprop['BORDER-TOP'] = $v;
791				$newprop['BORDER-RIGHT'] = $v;
792				$newprop['BORDER-BOTTOM'] = $v;
793				$newprop['BORDER-LEFT'] = $v;
794
795			} elseif ($k === 'BORDER-TOP') {
796
797				$newprop['BORDER-TOP'] = $this->_fix_borderStr($v);
798
799			} elseif ($k === 'BORDER-RIGHT') {
800
801				$newprop['BORDER-RIGHT'] = $this->_fix_borderStr($v);
802
803			} elseif ($k === 'BORDER-BOTTOM') {
804
805				$newprop['BORDER-BOTTOM'] = $this->_fix_borderStr($v);
806
807			} elseif ($k === 'BORDER-LEFT') {
808
809				$newprop['BORDER-LEFT'] = $this->_fix_borderStr($v);
810
811			} elseif ($k === 'BORDER-STYLE') {
812
813				$e = $this->expand24($v);
814
815				if (!empty($e)) {
816					$newprop['BORDER-TOP-STYLE'] = $e['T'];
817					$newprop['BORDER-RIGHT-STYLE'] = $e['R'];
818					$newprop['BORDER-BOTTOM-STYLE'] = $e['B'];
819					$newprop['BORDER-LEFT-STYLE'] = $e['L'];
820				}
821
822			} elseif ($k === 'BORDER-WIDTH') {
823
824				$e = $this->expand24($v);
825				if (!empty($e)) {
826					$newprop['BORDER-TOP-WIDTH'] = $e['T'];
827					$newprop['BORDER-RIGHT-WIDTH'] = $e['R'];
828					$newprop['BORDER-BOTTOM-WIDTH'] = $e['B'];
829					$newprop['BORDER-LEFT-WIDTH'] = $e['L'];
830				}
831
832			} elseif ($k === 'BORDER-COLOR') {
833
834				$e = $this->expand24($v);
835				if (!empty($e)) {
836					$newprop['BORDER-TOP-COLOR'] = $e['T'];
837					$newprop['BORDER-RIGHT-COLOR'] = $e['R'];
838					$newprop['BORDER-BOTTOM-COLOR'] = $e['B'];
839					$newprop['BORDER-LEFT-COLOR'] = $e['L'];
840				}
841
842			} elseif ($k === 'BORDER-SPACING') {
843
844				$prop = preg_split('/\s+/', trim($v));
845				if (count($prop) == 1) {
846					$newprop['BORDER-SPACING-H'] = $prop[0];
847					$newprop['BORDER-SPACING-V'] = $prop[0];
848				} elseif (count($prop) == 2) {
849					$newprop['BORDER-SPACING-H'] = $prop[0];
850					$newprop['BORDER-SPACING-V'] = $prop[1];
851				}
852
853			} elseif ($k === 'TEXT-OUTLINE') {
854
855				$prop = preg_split('/\s+/', trim($v));
856
857				if (strtolower(trim($v)) === 'none') {
858					$newprop['TEXT-OUTLINE'] = 'none';
859				} elseif (count($prop) == 2) {
860					$newprop['TEXT-OUTLINE-WIDTH'] = $prop[0];
861					$newprop['TEXT-OUTLINE-COLOR'] = $prop[1];
862				} elseif (count($prop) == 3) {
863					$newprop['TEXT-OUTLINE-WIDTH'] = $prop[0];
864					$newprop['TEXT-OUTLINE-COLOR'] = $prop[2];
865				}
866
867			} elseif ($k === 'SIZE') {
868
869				$prop = preg_split('/\s+/', trim($v));
870
871				if (preg_match('/(auto|portrait|landscape)/', $prop[0])) {
872					$newprop['SIZE'] = strtoupper($prop[0]);
873				} elseif (count($prop) == 1) {
874					$newprop['SIZE']['W'] = $this->sizeConverter->convert($prop[0]);
875					$newprop['SIZE']['H'] = $this->sizeConverter->convert($prop[0]);
876				} elseif (count($prop) == 2) {
877					$newprop['SIZE']['W'] = $this->sizeConverter->convert($prop[0]);
878					$newprop['SIZE']['H'] = $this->sizeConverter->convert($prop[1]);
879				}
880
881			} elseif ($k === 'SHEET-SIZE') {
882
883				$prop = preg_split('/\s+/', trim($v));
884
885				if (count($prop) == 2) {
886
887					$newprop['SHEET-SIZE'] = [$this->sizeConverter->convert($prop[0]), $this->sizeConverter->convert($prop[1])];
888
889				} else {
890
891					if (preg_match('/([0-9a-zA-Z]*)-L/i', $v, $m)) { // e.g. A4-L = A$ landscape
892						$ft = PageFormat::getSizeFromName($m[1]);
893						$format = [$ft[1], $ft[0]];
894					} else {
895						$format = PageFormat::getSizeFromName($v);
896					}
897					if ($format) {
898						$newprop['SHEET-SIZE'] = [$format[0] / Mpdf::SCALE, $format[1] / Mpdf::SCALE];
899					}
900
901				}
902
903			} elseif ($k === 'BACKGROUND') {
904
905				$bg = $this->parseCSSbackground($v);
906
907				if ($bg['c']) {
908					$newprop['BACKGROUND-COLOR'] = $bg['c'];
909				} else {
910					$newprop['BACKGROUND-COLOR'] = 'transparent';
911				}
912
913				if ($bg['i']) {
914					$newprop['BACKGROUND-IMAGE'] = $bg['i'];
915					if ($bg['r']) {
916						$newprop['BACKGROUND-REPEAT'] = $bg['r'];
917					}
918					if ($bg['p']) {
919						$newprop['BACKGROUND-POSITION'] = $bg['p'];
920					}
921				} else {
922					$newprop['BACKGROUND-IMAGE'] = '';
923				}
924
925			} elseif ($k === 'BACKGROUND-IMAGE') {
926
927				if (preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient\(.*\)/i', $v, $m)) {
928					$newprop['BACKGROUND-IMAGE'] = $m[0];
929					continue;
930				}
931
932				if (preg_match('/url\([\'\"]{0,1}(.*?)[\'\"]{0,1}\)/i', $v, $m)) {
933					$newprop['BACKGROUND-IMAGE'] = $m[1];
934				} elseif (strtolower($v) === 'none') {
935					$newprop['BACKGROUND-IMAGE'] = '';
936				}
937
938			} elseif ($k === 'BACKGROUND-REPEAT') {
939
940				if (preg_match('/(repeat-x|repeat-y|no-repeat|repeat)/i', $v, $m)) {
941					$newprop['BACKGROUND-REPEAT'] = strtolower($m[1]);
942				}
943
944			} elseif ($k === 'BACKGROUND-POSITION') {
945
946				$s = $v;
947				$bits = preg_split('/\s+/', trim($s));
948
949				// These should be Position x1 or x2
950				if (count($bits) === 1) {
951					if (false !== strpos($bits[0], 'bottom')) {
952						$bg['p'] = '50% 100%';
953					} elseif (false !== strpos($bits[0], 'top')) {
954						$bg['p'] = '50% 0%';
955					} else {
956						$bg['p'] = $bits[0] . ' 50%';
957					}
958
959				} elseif (count($bits) === 2) {
960					// Can be either right center or center right
961					if (preg_match('/(top|bottom)/', $bits[0]) || preg_match('/(left|right)/', $bits[1])) {
962						$bg['p'] = $bits[1] . ' ' . $bits[0];
963					} else {
964						$bg['p'] = $bits[0] . ' ' . $bits[1];
965					}
966				}
967
968				if (isset($bg['p'])) {
969
970					$bg['p'] = preg_replace('/(left|top)/', '0%', $bg['p']);
971					$bg['p'] = preg_replace('/(right|bottom)/', '100%', $bg['p']);
972					$bg['p'] = preg_replace('/(center)/', '50%', $bg['p']);
973
974					if (!preg_match('/[\-]{0,1}\d+(in|cm|mm|pt|pc|em|ex|px|%)* [\-]{0,1}\d+(in|cm|mm|pt|pc|em|ex|px|%)*/', $bg['p'])) {
975						$bg['p'] = false;
976					}
977
978					$newprop['BACKGROUND-POSITION'] = $bg['p'];
979				}
980
981			} elseif ($k === 'IMAGE-ORIENTATION') {
982
983				if (preg_match('/([\-]*[0-9\.]+)(deg|grad|rad)/i', $v, $m)) {
984
985					$angle = $m[1] + 0;
986
987					if (strtolower($m[2]) === 'grad') {
988						$angle *= (360 / 400);
989					} elseif (strtolower($m[2]) === 'rad') {
990						$angle = rad2deg($angle);
991					}
992
993					while ($angle < 0) {
994						$angle += 360;
995					}
996
997					$angle %= 360;
998					$angle /= 90;
999					$angle = round($angle) * 90;
1000
1001					$newprop['IMAGE-ORIENTATION'] = $angle;
1002				}
1003
1004			} elseif ($k === 'TEXT-ALIGN') {
1005
1006				if (preg_match('/["\'](.){1}["\']/i', $v, $m)) {
1007
1008					$d = array_search($m[1], $this->mpdf->decimal_align);
1009
1010					if ($d !== false) {
1011						$newprop['TEXT-ALIGN'] = $d;
1012					}
1013					if (preg_match('/(center|left|right)/i', $v, $m)) {
1014						$newprop['TEXT-ALIGN'] .= strtoupper(substr($m[1], 0, 1));
1015					} else {
1016						$newprop['TEXT-ALIGN'] .= 'R';
1017					} // default = R
1018
1019				} elseif (preg_match('/["\'](\\\[a-fA-F0-9]{1,6})["\']/i', $v, $m)) {
1020
1021					$utf8 = UtfString::codeHex2utf(substr($m[1], 1, 6));
1022					$d = array_search($utf8, $this->mpdf->decimal_align);
1023
1024					if ($d !== false) {
1025						$newprop['TEXT-ALIGN'] = $d;
1026					}
1027
1028					if (preg_match('/(center|left|right)/i', $v, $m)) {
1029						$newprop['TEXT-ALIGN'] .= strtoupper(substr($m[1], 0, 1));
1030					} else {
1031						$newprop['TEXT-ALIGN'] .= 'R';
1032					} // default = R
1033
1034				} else {
1035					$newprop[$k] = $v;
1036				}
1037
1038			} elseif ($k === 'LIST-STYLE') {
1039
1040				if (preg_match('/none/i', $v, $m)) {
1041					$newprop['LIST-STYLE-TYPE'] = 'none';
1042					$newprop['LIST-STYLE-IMAGE'] = 'none';
1043				}
1044
1045				if (preg_match('/(lower-roman|upper-roman|lower-latin|lower-alpha|upper-latin|upper-alpha|decimal|disc|circle|square|arabic-indic|bengali|devanagari|gujarati|gurmukhi|kannada|malayalam|oriya|persian|tamil|telugu|thai|urdu|cambodian|khmer|lao|cjk-decimal|hebrew)/i', $v, $m)) {
1046					$newprop['LIST-STYLE-TYPE'] = strtolower(trim($m[1]));
1047				} elseif (preg_match('/U\+([a-fA-F0-9]+)/i', $v, $m)) {
1048					$newprop['LIST-STYLE-TYPE'] = strtolower(trim($m[1]));
1049				}
1050
1051				if (preg_match('/url\([\'\"]{0,1}(.*?)[\'\"]{0,1}\)/i', $v, $m)) {
1052					$newprop['LIST-STYLE-IMAGE'] = strtolower(trim($m[1]));
1053				}
1054
1055				if (preg_match('/(inside|outside)/i', $v, $m)) {
1056					$newprop['LIST-STYLE-POSITION'] = strtolower(trim($m[1]));
1057				}
1058
1059			} else {
1060				$newprop[$k] = $v;
1061			}
1062		}
1063
1064		return $newprop;
1065	}
1066
1067	function setCSSboxshadow($v)
1068	{
1069		$sh = [];
1070		$c = preg_match_all('/(rgba|rgb|device-cmyka|cmyka|device-cmyk|cmyk|hsla|hsl)\(.*?\)/', $v, $x); // mPDF 5.6.05
1071		for ($i = 0; $i < $c; $i++) {
1072			$col = preg_replace('/,/', '*', $x[0][$i]);
1073			$v = str_replace($x[0][$i], $col, $v);
1074		}
1075		$ss = explode(',', $v);
1076		foreach ($ss as $s) {
1077			$new = ['inset' => false, 'blur' => 0, 'spread' => 0];
1078			if (false !== stripos($s, 'inset')) {
1079				$new['inset'] = true;
1080				$s = preg_replace('/\s*inset\s*/', '', $s);
1081			}
1082			$p = explode(' ', trim($s));
1083			if (isset($p[0])) {
1084				$new['x'] = $this->sizeConverter->convert(trim($p[0]), $this->mpdf->blk[$this->mpdf->blklvl - 1]['inner_width'], $this->mpdf->FontSize, false);
1085			}
1086			if (isset($p[1])) {
1087				$new['y'] = $this->sizeConverter->convert(trim($p[1]), $this->mpdf->blk[$this->mpdf->blklvl - 1]['inner_width'], $this->mpdf->FontSize, false);
1088			}
1089			if (isset($p[2])) {
1090				if (preg_match('/^\s*[\.\-0-9]/', $p[2])) {
1091					$new['blur'] = $this->sizeConverter->convert(trim($p[2]), $this->mpdf->blk[$this->mpdf->blklvl - 1]['inner_width'], $this->mpdf->FontSize, false);
1092				} else {
1093					$new['col'] = $this->colorConverter->convert(preg_replace('/\*/', ',', $p[2]), $this->mpdf->PDFAXwarnings);
1094				}
1095				if (isset($p[3])) {
1096					if (preg_match('/^\s*[\.\-0-9]/', $p[3])) {
1097						$new['spread'] = $this->sizeConverter->convert(trim($p[3]), $this->mpdf->blk[$this->mpdf->blklvl - 1]['inner_width'], $this->mpdf->FontSize, false);
1098					} else {
1099						$new['col'] = $this->colorConverter->convert(preg_replace('/\*/', ',', $p[3]), $this->mpdf->PDFAXwarnings);
1100					}
1101					if (isset($p[4])) {
1102						$new['col'] = $this->colorConverter->convert(preg_replace('/\*/', ',', $p[4]), $this->mpdf->PDFAXwarnings);
1103					}
1104				}
1105			}
1106			if (empty($new['col'])) {
1107				$new['col'] = $this->colorConverter->convert('#888888', $this->mpdf->PDFAXwarnings);
1108			}
1109			if (isset($new['y'])) {
1110				array_unshift($sh, $new);
1111			}
1112		}
1113		return $sh;
1114	}
1115
1116	function setCSStextshadow($v)
1117	{
1118		$sh = [];
1119		$c = preg_match_all('/(rgba|rgb|device-cmyka|cmyka|device-cmyk|cmyk|hsla|hsl)\(.*?\)/', $v, $x); // mPDF 5.6.05
1120
1121		for ($i = 0; $i < $c; $i++) {
1122			$col = preg_replace('/,\s/', '*', $x[0][$i]);
1123			$v = str_replace($x[0][$i], $col, $v);
1124		}
1125
1126		$ss = explode(',', $v);
1127
1128		foreach ($ss as $s) {
1129
1130			$new = ['blur' => 0];
1131			$p = explode(' ', trim($s));
1132
1133			if (isset($p[0])) {
1134				$new['x'] = $this->sizeConverter->convert(trim($p[0]), $this->mpdf->FontSize, $this->mpdf->FontSize, false);
1135			}
1136
1137			if (isset($p[1])) {
1138				$new['y'] = $this->sizeConverter->convert(trim($p[1]), $this->mpdf->FontSize, $this->mpdf->FontSize, false);
1139			}
1140
1141			if (isset($p[2])) {
1142
1143				if (preg_match('/^\s*[\.\-0-9]/', $p[2])) {
1144
1145					$new['blur'] = $this->sizeConverter->convert(
1146						trim($p[2]),
1147						isset($this->mpdf->blk[$this->mpdf->blklvl]['inner_width']) ? $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'] : 0,
1148						$this->mpdf->FontSize,
1149						false
1150					);
1151
1152				} else {
1153					$new['col'] = $this->colorConverter->convert(preg_replace('/\*/', ',', $p[2]), $this->mpdf->PDFAXwarnings);
1154				}
1155
1156				if (isset($p[3])) {
1157					$new['col'] = $this->colorConverter->convert(preg_replace('/\*/', ',', $p[3]), $this->mpdf->PDFAXwarnings);
1158				}
1159			}
1160
1161			if (!isset($new['col']) || !$new['col']) {
1162				$new['col'] = $this->colorConverter->convert('#888888', $this->mpdf->PDFAXwarnings);
1163			}
1164
1165			if (isset($new['y'])) {
1166				array_unshift($sh, $new);
1167			}
1168
1169		}
1170
1171		return $sh;
1172	}
1173
1174	function parseCSSbackground($s)
1175	{
1176		$bg = ['c' => false, 'i' => false, 'r' => false, 'p' => false,];
1177		/* -- BACKGROUNDS -- */
1178		if (preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient\(.*\)/i', $s, $m)) {
1179			$bg['i'] = $m[0];
1180		} else {
1181			if (preg_match('/url\(/i', $s)) { /* -- END BACKGROUNDS -- */
1182				// If color, set and strip it off
1183				// mPDF 5.6.05
1184				if (preg_match('/^\s*(#[0-9a-fA-F]{3,6}|(rgba|rgb|device-cmyka|cmyka|device-cmyk|cmyk|hsla|hsl|spot)\(.*?\)|[a-zA-Z]{3,})\s+(url\(.*)/i', $s, $m)) {
1185					$bg['c'] = strtolower($m[1]);
1186					$s = $m[3];
1187				}
1188				/* -- BACKGROUNDS -- */
1189				if (preg_match('/url\([\'\"]{0,1}(.*?)[\'\"]{0,1}\)\s*(.*)/i', $s, $m)) {
1190					$bg['i'] = $m[1];
1191					$s = strtolower($m[2]);
1192					if (preg_match('/(repeat-x|repeat-y|no-repeat|repeat)/', $s, $m)) {
1193						$bg['r'] = $m[1];
1194					}
1195					// Remove repeat, attachment (discarded) and also any inherit
1196					$s = preg_replace('/(repeat-x|repeat-y|no-repeat|repeat|scroll|fixed|inherit)/', '', $s);
1197					$bits = preg_split('/\s+/', trim($s));
1198					// These should be Position x1 or x2
1199					if (count($bits) == 1) {
1200						if (false !== strpos($bits[0], 'bottom')) {
1201							$bg['p'] = '50% 100%';
1202						} elseif (false !== strpos($bits[0], 'top')) {
1203							$bg['p'] = '50% 0%';
1204						} else {
1205							$bg['p'] = $bits[0] . ' 50%';
1206						}
1207					} elseif (count($bits) == 2) {
1208						// Can be either right center or center right
1209						if (preg_match('/(top|bottom)/', $bits[0]) || preg_match('/(left|right)/', $bits[1])) {
1210							$bg['p'] = $bits[1] . ' ' . $bits[0];
1211						} else {
1212							$bg['p'] = $bits[0] . ' ' . $bits[1];
1213						}
1214					}
1215					if ($bg['p']) {
1216						$bg['p'] = preg_replace('/(left|top)/', '0%', $bg['p']);
1217						$bg['p'] = preg_replace('/(right|bottom)/', '100%', $bg['p']);
1218						$bg['p'] = preg_replace('/(center)/', '50%', $bg['p']);
1219						if (!preg_match('/[\-]{0,1}\d+(in|cm|mm|pt|pc|em|ex|px|%)* [\-]{0,1}\d+(in|cm|mm|pt|pc|em|ex|px|%)*/', $bg['p'])) {
1220							$bg['p'] = false;
1221						}
1222					}
1223				}
1224				/* -- END BACKGROUNDS -- */
1225			} elseif (preg_match('/^\s*(#[0-9a-fA-F]{3,6}|(rgba|rgb|device-cmyka|cmyka|device-cmyk|cmyk|hsla|hsl|spot)\(.*?\)|[a-zA-Z]{3,})/i', $s, $m)) {
1226				$bg['c'] = strtolower($m[1]);
1227			}
1228		} // mPDF 5.6.05
1229		return ($bg);
1230	}
1231
1232	function expand24($mp)
1233	{
1234		$prop = preg_split('/\s+/', trim($mp));
1235		$prop_count = count($prop);
1236
1237		if ($prop_count === 1) {
1238			return ['T' => $prop[0], 'R' => $prop[0], 'B' => $prop[0], 'L' => $prop[0]];
1239		}
1240
1241		if ($prop_count === 2) {
1242			return ['T' => $prop[0], 'R' => $prop[1], 'B' => $prop[0], 'L' => $prop[1]];
1243		}
1244
1245		if ($prop_count === 3) {
1246			return ['T' => $prop[0], 'R' => $prop[1], 'B' => $prop[2], 'L' => $prop[1]];
1247		}
1248
1249		// Ignore rule parts after first 4 values (most likely !important)
1250		if ($prop_count >= 4) {
1251			return ['T' => $prop[0], 'R' => $prop[1], 'B' => $prop[2], 'L' => $prop[3]];
1252		}
1253
1254		return [];
1255	}
1256
1257	/* -- BORDER-RADIUS -- */
1258
1259	function border_radius_expand($val, $k)
1260	{
1261		$b = [];
1262
1263		if ($k === 'BORDER-RADIUS') {
1264
1265			$hv = explode('/', trim($val));
1266			$prop = preg_split('/\s+/', trim($hv[0]));
1267
1268			if (count($prop) == 1) {
1269				$b['TL-H'] = $b['TR-H'] = $b['BR-H'] = $b['BL-H'] = $prop[0];
1270			} elseif (count($prop) == 2) {
1271				$b['TL-H'] = $b['BR-H'] = $prop[0];
1272				$b['TR-H'] = $b['BL-H'] = $prop[1];
1273			} elseif (count($prop) == 3) {
1274				$b['TL-H'] = $prop[0];
1275				$b['TR-H'] = $b['BL-H'] = $prop[1];
1276				$b['BR-H'] = $prop[2];
1277			} elseif (count($prop) == 4) {
1278				$b['TL-H'] = $prop[0];
1279				$b['TR-H'] = $prop[1];
1280				$b['BR-H'] = $prop[2];
1281				$b['BL-H'] = $prop[3];
1282			}
1283
1284			if (count($hv) == 2) {
1285				$prop = preg_split('/\s+/', trim($hv[1]));
1286				if (count($prop) == 1) {
1287					$b['TL-V'] = $b['TR-V'] = $b['BR-V'] = $b['BL-V'] = $prop[0];
1288				} elseif (count($prop) == 2) {
1289					$b['TL-V'] = $b['BR-V'] = $prop[0];
1290					$b['TR-V'] = $b['BL-V'] = $prop[1];
1291				} elseif (count($prop) == 3) {
1292					$b['TL-V'] = $prop[0];
1293					$b['TR-V'] = $b['BL-V'] = $prop[1];
1294					$b['BR-V'] = $prop[2];
1295				} elseif (count($prop) == 4) {
1296					$b['TL-V'] = $prop[0];
1297					$b['TR-V'] = $prop[1];
1298					$b['BR-V'] = $prop[2];
1299					$b['BL-V'] = $prop[3];
1300				}
1301			} else {
1302				$b['TL-V'] = Arrays::get($b, 'TL-H', 0);
1303				$b['TR-V'] = Arrays::get($b, 'TR-H', 0);
1304				$b['BL-V'] = Arrays::get($b, 'BL-H', 0);
1305				$b['BR-V'] = Arrays::get($b, 'BR-H', 0);
1306			}
1307
1308			return $b;
1309		}
1310
1311		// Parse 2
1312		$prop = preg_split('/\s+/', trim($val));
1313
1314		if (count($prop) == 1) {
1315			$h = $v = $val;
1316		} else {
1317			$h = $prop[0];
1318			$v = $prop[1];
1319		}
1320
1321		if ($h == 0 || $v == 0) {
1322			$h = $v = 0;
1323		}
1324
1325		if ($k === 'BORDER-TOP-LEFT-RADIUS') {
1326			$b['TL-H'] = $h;
1327			$b['TL-V'] = $v;
1328		} elseif ($k === 'BORDER-TOP-RIGHT-RADIUS') {
1329			$b['TR-H'] = $h;
1330			$b['TR-V'] = $v;
1331		} elseif ($k === 'BORDER-BOTTOM-LEFT-RADIUS') {
1332			$b['BL-H'] = $h;
1333			$b['BL-V'] = $v;
1334		} elseif ($k === 'BORDER-BOTTOM-RIGHT-RADIUS') {
1335			$b['BR-H'] = $h;
1336			$b['BR-V'] = $v;
1337		}
1338
1339		return $b;
1340	}
1341	/* -- END BORDER-RADIUS -- */
1342
1343	function _mergeCSS($p, &$t)
1344	{
1345		// Save Cascading CSS e.g. "div.topic p" at this block level
1346		if (isset($p) && $p) {
1347			if ($t) {
1348				$t = $this->array_merge_recursive_unique($t, $p);
1349			} else {
1350				$t = $p;
1351			}
1352		}
1353	}
1354
1355	// for CSS handling
1356	function array_merge_recursive_unique($array1, $array2)
1357	{
1358		$arrays = func_get_args();
1359		$narrays = count($arrays);
1360		$ret = $arrays[0];
1361		for ($i = 1; $i < $narrays; $i ++) {
1362			foreach ($arrays[$i] as $key => $value) {
1363				if (((string) $key) === ((string) ((int) $key))) { // integer or string as integer key - append
1364					$ret[] = $value;
1365				} else { // string key - merge
1366					if (is_array($value) && isset($ret[$key])) {
1367						$ret[$key] = $this->array_merge_recursive_unique($ret[$key], $value);
1368					} else {
1369						$ret[$key] = $value;
1370					}
1371				}
1372			}
1373		}
1374		return $ret;
1375	}
1376
1377	function _mergeFullCSS($p, &$t, $tag, $classes, $id, $lang)
1378	{
1379	// mPDF 6
1380		if (isset($p[$tag])) {
1381			$this->_mergeCSS($p[$tag], $t);
1382		}
1383		// STYLESHEET CLASS e.g. .smallone{}  .redletter{}
1384		foreach ($classes as $class) {
1385			if (isset($p['CLASS>>' . $class])) {
1386				$this->_mergeCSS($p['CLASS>>' . $class], $t);
1387			}
1388		}
1389		// STYLESHEET nth-child SELECTOR e.g. tr:nth-child(odd)  td:nth-child(2n+1)
1390		if ($tag === 'TR' && isset($p) && $p) {
1391			foreach ($p as $k => $val) {
1392				if (preg_match('/' . $tag . '>>SELECTORNTHCHILD>>(.*)/', $k, $m)) {
1393					$select = false;
1394					if ($tag === 'TR') {
1395						$row = $this->mpdf->row;
1396						$thnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) : 0);
1397						$tfnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) : 0);
1398						if ($this->mpdf->tabletfoot) {
1399							$row -= $thnr;
1400						} elseif (!$this->mpdf->tablethead) {
1401							$row -= ($thnr + $tfnr);
1402						}
1403						if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1404							$select = $this->_nthchild($a, $row);
1405						}
1406					} elseif ($tag === 'TD' || $tag === 'TH') {
1407						if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1408							$select = $this->_nthchild($a, $this->mpdf->col);
1409						}
1410					}
1411					if ($select) {
1412						$this->_mergeCSS($p[$tag . '>>SELECTORNTHCHILD>>' . $m[1]], $t);
1413					}
1414				}
1415			}
1416		}
1417		// STYLESHEET CLASS e.g. [lang=fr]{} or :lang(fr)
1418		if (isset($lang) && isset($p['LANG>>' . $lang])) {
1419			$this->_mergeCSS($p['LANG>>' . $lang], $t);
1420		}
1421		// STYLESHEET CLASS e.g. #smallone{}  #redletter{}
1422		if (isset($id) && isset($p['ID>>' . $id])) {
1423			$this->_mergeCSS($p['ID>>' . $id], $t);
1424		}
1425
1426		// STYLESHEET CLASS e.g. .smallone{}  .redletter{}
1427		foreach ($classes as $class) {
1428			if (isset($p[$tag . '>>CLASS>>' . $class])) {
1429				$this->_mergeCSS($p[$tag . '>>CLASS>>' . $class], $t);
1430			}
1431		}
1432		// STYLESHEET CLASS e.g. [lang=fr]{} or :lang(fr)
1433		if (isset($lang) && isset($p[$tag . '>>LANG>>' . $lang])) {
1434			$this->_mergeCSS($p[$tag . '>>LANG>>' . $lang], $t);
1435		}
1436		// STYLESHEET CLASS e.g. #smallone{}  #redletter{}
1437		if (isset($id) && isset($p[$tag . '>>ID>>' . $id])) {
1438			$this->_mergeCSS($p[$tag . '>>ID>>' . $id], $t);
1439		}
1440	}
1441
1442	function setBorderDominance($prop, $val)
1443	{
1444		if (!empty($prop['BORDER-LEFT'])) {
1445			$this->cell_border_dominance_L = $val;
1446		}
1447		if (!empty($prop['BORDER-RIGHT'])) {
1448			$this->cell_border_dominance_R = $val;
1449		}
1450		if (!empty($prop['BORDER-TOP'])) {
1451			$this->cell_border_dominance_T = $val;
1452		}
1453		if (!empty($prop['BORDER-BOTTOM'])) {
1454			$this->cell_border_dominance_B = $val;
1455		}
1456	}
1457
1458	function _set_mergedCSS(&$m, &$p, $d = true, $bd = false)
1459	{
1460		if (isset($m)) {
1461			if ((isset($m['depth']) && $m['depth'] > 1) || $d == false) {  // include check for 'depth'
1462				if ($bd) {
1463					$this->setBorderDominance($m, $bd);
1464				} // *TABLES*
1465				if (is_array($m)) {
1466					$p = array_merge($p, $m);
1467					$this->_mergeBorders($p, $m);
1468				}
1469			}
1470		}
1471	}
1472
1473	function _mergeBorders(&$b, &$a)
1474	{
1475	// Merges $a['BORDER-TOP-STYLE'] to $b['BORDER-TOP'] etc.
1476		foreach (['TOP', 'RIGHT', 'BOTTOM', 'LEFT'] as $side) {
1477			foreach (['STYLE', 'WIDTH', 'COLOR'] as $el) {
1478				if (isset($a['BORDER-' . $side . '-' . $el])) { // e.g. $b['BORDER-TOP-STYLE']
1479					$s = trim($a['BORDER-' . $side . '-' . $el]);
1480					if (isset($b['BORDER-' . $side])) { // e.g. $b['BORDER-TOP']
1481						$p = trim($b['BORDER-' . $side]);
1482					} else {
1483						$p = '';
1484					}
1485					if ($el === 'STYLE') {
1486						if ($p) {
1487							$b['BORDER-' . $side] = preg_replace('/(\S+)\s+(\S+)\s+(\S+)/', '\\1 ' . $s . ' \\3', $p);
1488						} else {
1489							$b['BORDER-' . $side] = '0px ' . $s . ' #000000';
1490						}
1491					} elseif ($el === 'WIDTH') {
1492						if ($p) {
1493							$b['BORDER-' . $side] = preg_replace('/(\S+)\s+(\S+)\s+(\S+)/', $s . ' \\2 \\3', $p);
1494						} else {
1495							$b['BORDER-' . $side] = $s . ' none #000000';
1496						}
1497					} elseif ($el === 'COLOR') {
1498						if ($p) {
1499							$b['BORDER-' . $side] = preg_replace('/(\S+)\s+(\S+)\s+(\S+)/', '\\1 \\2 ' . $s, $p);
1500						} else {
1501							$b['BORDER-' . $side] = '0px none ' . $s;
1502						}
1503					}
1504				}
1505			}
1506		}
1507	}
1508
1509	function MergeCSS($inherit, $tag, $attr)
1510	{
1511		$p = [];
1512
1513		$attr = is_array($attr) ? $attr : [];
1514
1515		$classes = [];
1516		if (isset($attr['CLASS'])) {
1517			$classes = array_map(function ($combination) {
1518				return join('.', $combination);
1519			}, Arrays::allUniqueSortedCombinations(preg_split('/\s+/', $attr['CLASS'])));
1520		}
1521		if (!isset($attr['ID'])) {
1522			$attr['ID'] = '';
1523		}
1524		// mPDF 6
1525		$shortlang = '';
1526		if (!isset($attr['LANG'])) {
1527			$attr['LANG'] = '';
1528		} else {
1529			$attr['LANG'] = strtolower($attr['LANG']);
1530			if (strlen($attr['LANG']) == 5) {
1531				$shortlang = substr($attr['LANG'], 0, 2);
1532			}
1533		}
1534
1535		/* -- TABLES -- */
1536
1537		// Set Inherited properties
1538		if ($inherit === 'TOPTABLE') { // $tag = TABLE
1539
1540			// Save Cascading CSS e.g. "div.topic p" at this block level
1541			if (isset($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'])) {
1542				$this->tablecascadeCSS[0] = $this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'];
1543			} else {
1544				$this->tablecascadeCSS[0] = $this->cascadeCSS;
1545			}
1546		}
1547
1548		// Set Inherited properties
1549		if ($inherit === 'TOPTABLE' || $inherit === 'TABLE') {
1550
1551			// Cascade everything from last level that is not an actual property, or defined by current tag/attributes
1552			if (isset($this->tablecascadeCSS[$this->tbCSSlvl - 1]) && is_array($this->tablecascadeCSS[$this->tbCSSlvl - 1])) {
1553				foreach ($this->tablecascadeCSS[$this->tbCSSlvl - 1] as $k => $v) {
1554					$this->tablecascadeCSS[$this->tbCSSlvl][$k] = $v;
1555				}
1556			}
1557
1558			$this->_mergeFullCSS(
1559				$this->cascadeCSS,
1560				$this->tablecascadeCSS[$this->tbCSSlvl],
1561				$tag,
1562				$classes,
1563				$attr['ID'],
1564				$attr['LANG']
1565			);
1566
1567			// Cascading forward CSS e.g. "table.topic td" for this table in $this->tablecascadeCSS
1568			// STYLESHEET TAG e.g. table
1569			if (isset($this->tablecascadeCSS[$this->tbCSSlvl - 1])) {
1570				$this->_mergeFullCSS(
1571					$this->tablecascadeCSS[$this->tbCSSlvl - 1],
1572					$this->tablecascadeCSS[$this->tbCSSlvl],
1573					$tag,
1574					$classes,
1575					$attr['ID'],
1576					$attr['LANG']
1577				);
1578			}
1579		}
1580
1581		/* -- END TABLES -- */
1582
1583		//===============================================
1584		// Set Inherited properties
1585		if ($inherit === 'BLOCK') {
1586			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS']) && is_array($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'])) {
1587				foreach ($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'] as $k => $v) {
1588					$this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'][$k] = $v;
1589				}
1590			}
1591
1592			//===============================================
1593			// Save Cascading CSS e.g. "div.topic p" at this block level
1594			$this->_mergeFullCSS($this->cascadeCSS, $this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'], $tag, $classes, $attr['ID'], $attr['LANG']);
1595			//===============================================
1596			// Cascading forward CSS
1597			//===============================================
1598			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1])) {
1599				$this->_mergeFullCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'], $this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'], $tag, $classes, $attr['ID'], $attr['LANG']);
1600			}
1601			//===============================================
1602			// Block properties which are inherited
1603			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['margin_collapse']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['margin_collapse']) {
1604				$p['MARGIN-COLLAPSE'] = 'COLLAPSE';
1605			} // custom tag, but follows CSS principle that border-collapse is inherited
1606			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['line_height']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_height']) {
1607				$p['LINE-HEIGHT'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_height'];
1608			}
1609			// mPDF 6
1610			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_strategy']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_strategy']) {
1611				$p['LINE-STACKING-STRATEGY'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_strategy'];
1612			}
1613			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_shift']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_shift']) {
1614				$p['LINE-STACKING-SHIFT'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['line_stacking_shift'];
1615			}
1616
1617			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['direction']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['direction']) {
1618				$p['DIRECTION'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['direction'];
1619			}
1620			// mPDF 6  Lists
1621			if ($tag === 'LI') {
1622				if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_type']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_type']) {
1623					$p['LIST-STYLE-TYPE'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_type'];
1624				}
1625			}
1626			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_image']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_image']) {
1627				$p['LIST-STYLE-IMAGE'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_image'];
1628			}
1629			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_position']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_position']) {
1630				$p['LIST-STYLE-POSITION'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['list_style_position'];
1631			}
1632
1633			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['align']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['align']) {
1634				if ($this->mpdf->blk[$this->mpdf->blklvl - 1]['align'] === 'L') {
1635					$p['TEXT-ALIGN'] = 'left';
1636				} elseif ($this->mpdf->blk[$this->mpdf->blklvl - 1]['align'] === 'J') {
1637					$p['TEXT-ALIGN'] = 'justify';
1638				} elseif ($this->mpdf->blk[$this->mpdf->blklvl - 1]['align'] === 'R') {
1639					$p['TEXT-ALIGN'] = 'right';
1640				} elseif ($this->mpdf->blk[$this->mpdf->blklvl - 1]['align'] === 'C') {
1641					$p['TEXT-ALIGN'] = 'center';
1642				}
1643			}
1644			if ($this->mpdf->ColActive || $this->mpdf->keep_block_together) {
1645				if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['bgcolor']) && $this->mpdf->blk[$this->mpdf->blklvl - 1]['bgcolor']) { // Doesn't officially inherit, but default value is transparent (?=inherited)
1646					$cor = $this->mpdf->blk[$this->mpdf->blklvl - 1]['bgcolorarray'];
1647					$p['BACKGROUND-COLOR'] = $this->colorConverter->colAtoString($cor);
1648				}
1649			}
1650
1651			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['text_indent']) && ($this->mpdf->blk[$this->mpdf->blklvl - 1]['text_indent'] || $this->mpdf->blk[$this->mpdf->blklvl - 1]['text_indent'] === 0)) {
1652				$p['TEXT-INDENT'] = $this->mpdf->blk[$this->mpdf->blklvl - 1]['text_indent'];
1653			}
1654			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1]['InlineProperties'])) {
1655				$biilp = $this->mpdf->blk[$this->mpdf->blklvl - 1]['InlineProperties'];
1656				$this->inlinePropsToCSS($biilp, $p); // mPDF 5.7.1
1657			} else {
1658				$biilp = null;
1659			}
1660		}
1661		//===============================================
1662		//===============================================
1663		// INLINE HTML ATTRIBUTES e.g. .. ALIGN="CENTER">
1664		// mPDF 6 (added)
1665		if (isset($attr['DIR']) && $attr['DIR'] != '') {
1666			$p['DIRECTION'] = $attr['DIR'];
1667		}
1668		// mPDF 6 (moved)
1669		if (isset($attr['LANG']) && $attr['LANG'] != '') {
1670			$p['LANG'] = $attr['LANG'];
1671		}
1672		if (isset($attr['COLOR']) && $attr['COLOR'] != '') {
1673			$p['COLOR'] = $attr['COLOR'];
1674		}
1675
1676		if ($tag !== 'INPUT') {
1677			if (isset($attr['WIDTH']) && $attr['WIDTH'] != '') {
1678				$p['WIDTH'] = $attr['WIDTH'];
1679			}
1680			if (isset($attr['HEIGHT']) && $attr['HEIGHT'] != '') {
1681				$p['HEIGHT'] = $attr['HEIGHT'];
1682			}
1683		}
1684		if ($tag === 'FONT') {
1685			if (isset($attr['FACE'])) {
1686				$p['FONT-FAMILY'] = $attr['FACE'];
1687			}
1688			if (isset($attr['SIZE']) && $attr['SIZE'] != '') {
1689				$s = '';
1690				if ($attr['SIZE'] === '+1') {
1691					$s = '120%';
1692				} elseif ($attr['SIZE'] === '-1') {
1693					$s = '86%';
1694				} elseif ($attr['SIZE'] === '1') {
1695					$s = 'XX-SMALL';
1696				} elseif ($attr['SIZE'] == '2') {
1697					$s = 'X-SMALL';
1698				} elseif ($attr['SIZE'] == '3') {
1699					$s = 'SMALL';
1700				} elseif ($attr['SIZE'] == '4') {
1701					$s = 'MEDIUM';
1702				} elseif ($attr['SIZE'] == '5') {
1703					$s = 'LARGE';
1704				} elseif ($attr['SIZE'] == '6') {
1705					$s = 'X-LARGE';
1706				} elseif ($attr['SIZE'] == '7') {
1707					$s = 'XX-LARGE';
1708				}
1709				if ($s) {
1710					$p['FONT-SIZE'] = $s;
1711				}
1712			}
1713		}
1714		if (isset($attr['VALIGN']) && $attr['VALIGN'] != '') {
1715			$p['VERTICAL-ALIGN'] = $attr['VALIGN'];
1716		}
1717		if (isset($attr['VSPACE']) && $attr['VSPACE'] != '') {
1718			$p['MARGIN-TOP'] = $attr['VSPACE'];
1719			$p['MARGIN-BOTTOM'] = $attr['VSPACE'];
1720		}
1721		if (isset($attr['HSPACE']) && $attr['HSPACE'] != '') {
1722			$p['MARGIN-LEFT'] = $attr['HSPACE'];
1723			$p['MARGIN-RIGHT'] = $attr['HSPACE'];
1724		}
1725		//===============================================
1726		//===============================================
1727		// DEFAULT for this TAG set in DefaultCSS
1728		if (isset($this->mpdf->defaultCSS[$tag])) {
1729			$zp = $this->fixCSS($this->mpdf->defaultCSS[$tag]);
1730			if (is_array($zp)) {  // Default overwrites Inherited
1731				$p = array_merge($p, $zp);  // !! Note other way round !!
1732				$this->_mergeBorders($p, $zp);
1733			}
1734		}
1735		//===============================================
1736		/* -- TABLES -- */
1737		// mPDF 5.7.3
1738		// cellSpacing overwrites TABLE default but not specific CSS set on table
1739		if ($tag === 'TABLE' && isset($attr['CELLSPACING'])) {
1740			$p['BORDER-SPACING-H'] = $p['BORDER-SPACING-V'] = $attr['CELLSPACING'];
1741		}
1742		// cellPadding overwrites TD/TH default but not specific CSS set on cell
1743		if (($tag === 'TD' || $tag === 'TH') && isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding']) && ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'] || $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'] === '0')) {  // mPDF 5.7.3
1744			$p['PADDING-LEFT'] = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'];
1745			$p['PADDING-RIGHT'] = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'];
1746			$p['PADDING-TOP'] = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'];
1747			$p['PADDING-BOTTOM'] = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cell_padding'];
1748		}
1749		/* -- END TABLES -- */
1750		//===============================================
1751		// STYLESHEET TAG e.g. h1  p  div  table
1752		if (isset($this->CSS[$tag]) && $this->CSS[$tag]) {
1753			$zp = $this->CSS[$tag];
1754			if ($tag === 'TD' || $tag === 'TH') {
1755				$this->setBorderDominance($zp, 9);
1756			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1757			if (is_array($zp)) {
1758				$p = array_merge($p, $zp);
1759				$this->_mergeBorders($p, $zp);
1760			}
1761		}
1762		//===============================================
1763		// STYLESHEET CLASS e.g. .smallone{}  .redletter{}
1764		foreach ($classes as $class) {
1765			$zp = [];
1766			if (isset($this->CSS['CLASS>>' . $class]) && $this->CSS['CLASS>>' . $class]) {
1767				$zp = $this->CSS['CLASS>>' . $class];
1768			}
1769			if ($tag === 'TD' || $tag === 'TH') {
1770				$this->setBorderDominance($zp, 9);
1771			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1772			if (is_array($zp)) {
1773				$p = array_merge($p, $zp);
1774				$this->_mergeBorders($p, $zp);
1775			}
1776		}
1777		//===============================================
1778		/* -- TABLES -- */
1779		// STYLESHEET nth-child SELECTOR e.g. tr:nth-child(odd)  td:nth-child(2n+1)
1780		if ($tag === 'TR' || $tag === 'TD' || $tag === 'TH') {
1781			foreach ($this->CSS as $k => $val) {
1782				if (preg_match('/' . $tag . '>>SELECTORNTHCHILD>>(.*)/', $k, $m)) {
1783					$select = false;
1784					if ($tag === 'TR') {
1785						$row = $this->mpdf->row;
1786						$thnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) : 0);
1787						$tfnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) : 0);
1788						if ($this->mpdf->tabletfoot) {
1789							$row -= $thnr;
1790						} elseif (!$this->mpdf->tablethead) {
1791							$row -= ($thnr + $tfnr);
1792						}
1793						if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1794							$select = $this->_nthchild($a, $row);
1795						}
1796					} elseif ($tag === 'TD' || $tag === 'TH') {
1797						if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1798							$select = $this->_nthchild($a, $this->mpdf->col);
1799						}
1800					}
1801					if ($select) {
1802						$zp = $this->CSS[$tag . '>>SELECTORNTHCHILD>>' . $m[1]];
1803						if ($tag === 'TD' || $tag === 'TH') {
1804							$this->setBorderDominance($zp, 9);
1805						}
1806						if (is_array($zp)) {
1807							$p = array_merge($p, $zp);
1808							$this->_mergeBorders($p, $zp);
1809						}
1810					}
1811				}
1812			}
1813		}
1814		/* -- END TABLES -- */
1815		//===============================================
1816		// STYLESHEET LANG e.g. [lang=fr]{} or :lang(fr)
1817		if (isset($attr['LANG'])) {
1818			if (isset($this->CSS['LANG>>' . $attr['LANG']]) && $this->CSS['LANG>>' . $attr['LANG']]) {
1819				$zp = $this->CSS['LANG>>' . $attr['LANG']];
1820				if ($tag === 'TD' || $tag === 'TH') {
1821					$this->setBorderDominance($zp, 9);
1822				} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1823				if (is_array($zp)) {
1824					$p = array_merge($p, $zp);
1825					$this->_mergeBorders($p, $zp);
1826				}
1827			} elseif (isset($this->CSS['LANG>>' . $shortlang]) && $this->CSS['LANG>>' . $shortlang]) {
1828				$zp = $this->CSS['LANG>>' . $shortlang];
1829				if ($tag === 'TD' || $tag === 'TH') {
1830					$this->setBorderDominance($zp, 9);
1831				} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1832				if (is_array($zp)) {
1833					$p = array_merge($p, $zp);
1834					$this->_mergeBorders($p, $zp);
1835				}
1836			}
1837		}
1838		//===============================================
1839		// STYLESHEET ID e.g. #smallone{}  #redletter{}
1840		if (isset($attr['ID']) && isset($this->CSS['ID>>' . $attr['ID']]) && $this->CSS['ID>>' . $attr['ID']]) {
1841			$zp = $this->CSS['ID>>' . $attr['ID']];
1842			if ($tag === 'TD' || $tag === 'TH') {
1843				$this->setBorderDominance($zp, 9);
1844			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1845			if (is_array($zp)) {
1846				$p = array_merge($p, $zp);
1847				$this->_mergeBorders($p, $zp);
1848			}
1849		}
1850
1851		//===============================================
1852		// STYLESHEET CLASS e.g. p.smallone{}  div.redletter{}
1853		foreach ($classes as $class) {
1854			$zp = [];
1855			if (isset($this->CSS[$tag . '>>CLASS>>' . $class]) && $this->CSS[$tag . '>>CLASS>>' . $class]) {
1856				$zp = $this->CSS[$tag . '>>CLASS>>' . $class];
1857			}
1858			if ($tag === 'TD' || $tag === 'TH') {
1859				$this->setBorderDominance($zp, 9);
1860			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1861			if (is_array($zp)) {
1862				$p = array_merge($p, $zp);
1863				$this->_mergeBorders($p, $zp);
1864			}
1865		}
1866		//===============================================
1867		// STYLESHEET LANG e.g. [lang=fr]{} or :lang(fr)
1868		if (isset($attr['LANG'])) {
1869			if (isset($this->CSS[$tag . '>>LANG>>' . $attr['LANG']]) && $this->CSS[$tag . '>>LANG>>' . $attr['LANG']]) {
1870				$zp = $this->CSS[$tag . '>>LANG>>' . $attr['LANG']];
1871				if ($tag === 'TD' || $tag === 'TH') {
1872					$this->setBorderDominance($zp, 9);
1873				} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1874				if (is_array($zp)) {
1875					$p = array_merge($p, $zp);
1876					$this->_mergeBorders($p, $zp);
1877				}
1878			} elseif (isset($this->CSS[$tag . '>>LANG>>' . $shortlang]) && $this->CSS[$tag . '>>LANG>>' . $shortlang]) {
1879				$zp = $this->CSS[$tag . '>>LANG>>' . $shortlang];
1880				if ($tag === 'TD' || $tag === 'TH') {
1881					$this->setBorderDominance($zp, 9);
1882				} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1883				if (is_array($zp)) {
1884					$p = array_merge($p, $zp);
1885					$this->_mergeBorders($p, $zp);
1886				}
1887			}
1888		}
1889		//===============================================
1890		// STYLESHEET CLASS e.g. p#smallone{}  div#redletter{}
1891		if (isset($attr['ID']) && isset($this->CSS[$tag . '>>ID>>' . $attr['ID']]) && $this->CSS[$tag . '>>ID>>' . $attr['ID']]) {
1892			$zp = $this->CSS[$tag . '>>ID>>' . $attr['ID']];
1893			if ($tag === 'TD' || $tag === 'TH') {
1894				$this->setBorderDominance($zp, 9);
1895			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1896			if (is_array($zp)) {
1897				$p = array_merge($p, $zp);
1898				$this->_mergeBorders($p, $zp);
1899			}
1900		}
1901		//===============================================
1902		// Cascaded e.g. div.class p only works for block level
1903		if ($inherit === 'BLOCK') {
1904			if (isset($this->mpdf->blk[$this->mpdf->blklvl - 1])) { // mPDF 6
1905				$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'][$tag], $p);
1906				foreach ($classes as $class) {
1907					$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS']['CLASS>>' . $class], $p);
1908				}
1909				$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS']['ID>>' . $attr['ID']], $p);
1910				foreach ($classes as $class) {
1911					$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'][$tag . '>>CLASS>>' . $class], $p);
1912				}
1913				$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl - 1]['cascadeCSS'][$tag . '>>ID>>' . $attr['ID']], $p);
1914			}
1915		} elseif ($inherit === 'INLINE') {
1916			$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'][$tag], $p);
1917			foreach ($classes as $class) {
1918				$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS']['CLASS>>' . $class], $p);
1919			}
1920			$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS']['ID>>' . $attr['ID']], $p);
1921			foreach ($classes as $class) {
1922				$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'][$tag . '>>CLASS>>' . $class], $p);
1923			}
1924			$this->_set_mergedCSS($this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'][$tag . '>>ID>>' . $attr['ID']], $p);
1925		} elseif ($inherit === 'TOPTABLE' || $inherit === 'TABLE') { // NB looks at $this->tablecascadeCSS-1 for cascading CSS
1926			if (isset($this->tablecascadeCSS[$this->tbCSSlvl - 1])) { // mPDF 6
1927				// false, 9 = don't check for 'depth' and do set border dominance
1928				$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1][$tag], $p, false, 9);
1929				foreach ($classes as $class) {
1930					$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1]['CLASS>>' . $class], $p, false, 9);
1931				}
1932				// STYLESHEET nth-child SELECTOR e.g. tr:nth-child(odd)  td:nth-child(2n+1)
1933				if ($tag === 'TR' || $tag === 'TD' || $tag === 'TH') {
1934					foreach ($this->tablecascadeCSS[$this->tbCSSlvl - 1] as $k => $val) {
1935						if (preg_match('/' . $tag . '>>SELECTORNTHCHILD>>(.*)/', $k, $m)) {
1936							$select = false;
1937							if ($tag === 'TR') {
1938								$row = $this->mpdf->row;
1939								$thnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead']) : 0);
1940								$tfnr = (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) ? count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) : 0);
1941								if ($this->mpdf->tabletfoot) {
1942									$row -= $thnr;
1943								} elseif (!$this->mpdf->tablethead) {
1944									$row -= ($thnr + $tfnr);
1945								}
1946								if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1947									$select = $this->_nthchild($a, $row);
1948								}
1949							} elseif ($tag === 'TD' || $tag === 'TH') {
1950								if (preg_match('/(([\-+]?\d*)?N([\-+]\d+)?|[\-+]?\d+|ODD|EVEN)/', $m[1], $a)) { // mPDF 5.7.4
1951									$select = $this->_nthchild($a, $this->mpdf->col);
1952								}
1953							}
1954							if ($select) {
1955								$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1][$tag . '>>SELECTORNTHCHILD>>' . $m[1]], $p, false, 9);
1956							}
1957						}
1958					}
1959				}
1960			}
1961			$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1]['ID>>' . $attr['ID']], $p, false, 9);
1962			foreach ($classes as $class) {
1963				$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1][$tag . '>>CLASS>>' . $class], $p, false, 9);
1964			}
1965			$this->_set_mergedCSS($this->tablecascadeCSS[$this->tbCSSlvl - 1][$tag . '>>ID>>' . $attr['ID']], $p, false, 9);
1966		}
1967
1968		// INLINE STYLE e.g. style="CSS:property"
1969		if (isset($attr['STYLE'])) {
1970			$zp = $this->readInlineCSS($attr['STYLE']);
1971			if ($tag === 'TD' || $tag === 'TH') {
1972				$this->setBorderDominance($zp, 9);
1973			} // *TABLES*	// *TABLES-ADVANCED-BORDERS*
1974			if (is_array($zp)) {
1975				$p = array_merge($p, $zp);
1976				$this->_mergeBorders($p, $zp);
1977			}
1978		}
1979
1980		return $p;
1981	}
1982
1983	// Convert inline Properties back to CSS
1984	function inlinePropsToCSS($bilp, &$p)
1985	{
1986		if (isset($bilp['family']) && $bilp['family']) {
1987			$p['FONT-FAMILY'] = $bilp['family'];
1988		}
1989		if (isset($bilp['I']) && $bilp['I']) {
1990			$p['FONT-STYLE'] = 'italic';
1991		}
1992		if (isset($bilp['sizePt']) && $bilp['sizePt']) {
1993			$p['FONT-SIZE'] = $bilp['sizePt'] . 'pt';
1994		}
1995		if (isset($bilp['B']) && $bilp['B']) {
1996			$p['FONT-WEIGHT'] = 'bold';
1997		}
1998		if (isset($bilp['colorarray']) && $bilp['colorarray']) {
1999			$cor = $bilp['colorarray'];
2000			$p['COLOR'] = $this->colorConverter->colAtoString($cor);
2001		}
2002		if (isset($bilp['lSpacingCSS']) && $bilp['lSpacingCSS']) {
2003			$p['LETTER-SPACING'] = $bilp['lSpacingCSS'];
2004		}
2005		if (isset($bilp['wSpacingCSS']) && $bilp['wSpacingCSS']) {
2006			$p['WORD-SPACING'] = $bilp['wSpacingCSS'];
2007		}
2008
2009		if (isset($bilp['textparam']) && $bilp['textparam']) {
2010			if (isset($bilp['textparam']['hyphens'])) {
2011				if ($bilp['textparam']['hyphens'] == 2) {
2012					$p['HYPHENS'] = 'none';
2013				}
2014				if ($bilp['textparam']['hyphens'] == 1) {
2015					$p['HYPHENS'] = 'auto';
2016				}
2017				if ($bilp['textparam']['hyphens'] == 0) {
2018					$p['HYPHENS'] = 'manual';
2019				}
2020			}
2021			if (isset($bilp['textparam']['outline-s']) && !$bilp['textparam']['outline-s']) {
2022				$p['TEXT-OUTLINE'] = 'none';
2023			}
2024			if (isset($bilp['textparam']['outline-COLOR']) && $bilp['textparam']['outline-COLOR']) {
2025				$p['TEXT-OUTLINE-COLOR'] = $this->colorConverter->colAtoString($bilp['textparam']['outline-COLOR']);
2026			}
2027			if (isset($bilp['textparam']['outline-WIDTH']) && $bilp['textparam']['outline-WIDTH']) {
2028				$p['TEXT-OUTLINE-WIDTH'] = $bilp['textparam']['outline-WIDTH'] . 'mm';
2029			}
2030		}
2031
2032		if (isset($bilp['textvar']) && $bilp['textvar']) {
2033			// CSS says text-decoration is not inherited, but IE7 does??
2034			if ($bilp['textvar'] & TextVars::FD_LINETHROUGH) {
2035				if ($bilp['textvar'] & TextVars::FD_UNDERLINE) {
2036					$p['TEXT-DECORATION'] = 'underline line-through';
2037				} else {
2038					$p['TEXT-DECORATION'] = 'line-through';
2039				}
2040			} elseif ($bilp['textvar'] & TextVars::FD_UNDERLINE) {
2041				$p['TEXT-DECORATION'] = 'underline';
2042			} else {
2043				$p['TEXT-DECORATION'] = 'none';
2044			}
2045
2046			if ($bilp['textvar'] & TextVars::FA_SUPERSCRIPT) {
2047				$p['VERTICAL-ALIGN'] = 'super';
2048			} elseif ($bilp['textvar'] & TextVars::FA_SUBSCRIPT) {
2049				$p['VERTICAL-ALIGN'] = 'sub';
2050			} else {
2051				$p['VERTICAL-ALIGN'] = 'baseline';
2052			}
2053
2054			if ($bilp['textvar'] & TextVars::FT_CAPITALIZE) {
2055				$p['TEXT-TRANSFORM'] = 'capitalize';
2056			} elseif ($bilp['textvar'] & TextVars::FT_UPPERCASE) {
2057				$p['TEXT-TRANSFORM'] = 'uppercase';
2058			} elseif ($bilp['textvar'] & TextVars::FT_LOWERCASE) {
2059				$p['TEXT-TRANSFORM'] = 'lowercase';
2060			} else {
2061				$p['TEXT-TRANSFORM'] = 'none';
2062			}
2063
2064			if ($bilp['textvar'] & TextVars::FC_KERNING) {
2065				$p['FONT-KERNING'] = 'normal';
2066			} // ignore 'auto' as default already applied
2067			//if (isset($bilp[ 'OTLtags' ]) && $bilp[ 'OTLtags' ]['Plus'] contains 'kern'
2068			else {
2069				$p['FONT-KERNING'] = 'none';
2070			}
2071
2072			if ($bilp['textvar'] & TextVars::FA_SUPERSCRIPT) {
2073				$p['FONT-VARIANT-POSITION'] = 'super';
2074			} //if (isset($bilp[ 'OTLtags' ]) && $bilp[ 'OTLtags' ]['Plus'] contains 'sups' / 'subs'
2075			elseif ($bilp['textvar'] & TextVars::FA_SUBSCRIPT) {
2076				$p['FONT-VARIANT-POSITION'] = 'sub';
2077			} else {
2078				$p['FONT-VARIANT-POSITION'] = 'normal';
2079			}
2080
2081			if ($bilp['textvar'] & TextVars::FC_SMALLCAPS) {
2082				$p['FONT-VARIANT-CAPS'] = 'small-caps';
2083			}
2084		}
2085		if (isset($bilp['fontLanguageOverride'])) {
2086			if ($bilp['fontLanguageOverride']) {
2087				$p['FONT-LANGUAGE-OVERRIDE'] = $bilp['fontLanguageOverride'];
2088			} else {
2089				$p['FONT-LANGUAGE-OVERRIDE'] = 'normal';
2090			}
2091		}
2092		// All the variations of font-variant-* we are going to set as font-feature-settings...
2093		if (isset($bilp['OTLtags']) && $bilp['OTLtags']) {
2094			$ffs = [];
2095			if (isset($bilp['OTLtags']['Minus']) && $bilp['OTLtags']['Minus']) {
2096				$f = preg_split('/\s+/', trim($bilp['OTLtags']['Minus']));
2097				foreach ($f as $ff) {
2098					$ffs[] = "'" . $ff . "' 0";
2099				}
2100			}
2101			if (isset($bilp['OTLtags']['FFMinus']) && $bilp['OTLtags']['FFMinus']) {
2102				$f = preg_split('/\s+/', trim($bilp['OTLtags']['FFMinus']));
2103				foreach ($f as $ff) {
2104					$ffs[] = "'" . $ff . "' 0";
2105				}
2106			}
2107			if (isset($bilp['OTLtags']['Plus']) && $bilp['OTLtags']['Plus']) {
2108				$f = preg_split('/\s+/', trim($bilp['OTLtags']['Plus']));
2109				foreach ($f as $ff) {
2110					$ffs[] = "'" . $ff . "' 1";
2111				}
2112			}
2113			if (isset($bilp['OTLtags']['FFPlus']) && $bilp['OTLtags']['FFPlus']) { // May contain numeric value e.g. salt4
2114				$f = preg_split('/\s+/', trim($bilp['OTLtags']['FFPlus']));
2115				foreach ($f as $ff) {
2116					if (strlen($ff) > 4) {
2117						$ffs[] = "'" . substr($ff, 0, 4) . "' " . substr($ff, 4);
2118					} else {
2119						$ffs[] = "'" . $ff . "' 1";
2120					}
2121				}
2122			}
2123			$p['FONT-FEATURE-SETTINGS'] = implode(', ', $ffs);
2124		}
2125	}
2126
2127	function PreviewBlockCSS($tag, $attr)
2128	{
2129		// Looks ahead from current block level to a new level
2130		$p = [];
2131
2132		$oldcascadeCSS = $this->mpdf->blk[$this->mpdf->blklvl]['cascadeCSS'];
2133		$classes = [];
2134		if (isset($attr['CLASS'])) {
2135			$classes = array_map(function ($combination) {
2136				return join('.', $combination);
2137			}, Arrays::allUniqueSortedCombinations(preg_split('/\s+/', $attr['CLASS'])));
2138		}
2139		//===============================================
2140		// DEFAULT for this TAG set in DefaultCSS
2141		if (isset($this->mpdf->defaultCSS[$tag])) {
2142			$zp = $this->fixCSS($this->mpdf->defaultCSS[$tag]);
2143			if (is_array($zp)) {
2144				$p = array_merge($zp, $p);
2145			} // Inherited overwrites default
2146		}
2147		// STYLESHEET TAG e.g. h1  p  div  table
2148		if (isset($this->CSS[$tag])) {
2149			$zp = $this->CSS[$tag];
2150			if (is_array($zp)) {
2151				$p = array_merge($p, $zp);
2152			}
2153		}
2154		// STYLESHEET CLASS e.g. .smallone{}  .redletter{}
2155		foreach ($classes as $class) {
2156			$zp = [];
2157			if (isset($this->CSS['CLASS>>' . $class])) {
2158				$zp = $this->CSS['CLASS>>' . $class];
2159			}
2160			if (is_array($zp)) {
2161				$p = array_merge($p, $zp);
2162			}
2163		}
2164		// STYLESHEET ID e.g. #smallone{}  #redletter{}
2165		if (isset($attr['ID']) && isset($this->CSS['ID>>' . $attr['ID']])) {
2166			$zp = $this->CSS['ID>>' . $attr['ID']];
2167			if (is_array($zp)) {
2168				$p = array_merge($p, $zp);
2169			}
2170		}
2171		// STYLESHEET CLASS e.g. p.smallone{}  div.redletter{}
2172		foreach ($classes as $class) {
2173			$zp = [];
2174			if (isset($this->CSS[$tag . '>>CLASS>>' . $class])) {
2175				$zp = $this->CSS[$tag . '>>CLASS>>' . $class];
2176			}
2177			if (is_array($zp)) {
2178				$p = array_merge($p, $zp);
2179			}
2180		}
2181		// STYLESHEET CLASS e.g. p#smallone{}  div#redletter{}
2182		if (isset($attr['ID']) && isset($this->CSS[$tag . '>>ID>>' . $attr['ID']])) {
2183			$zp = $this->CSS[$tag . '>>ID>>' . $attr['ID']];
2184			if (is_array($zp)) {
2185				$p = array_merge($p, $zp);
2186			}
2187		}
2188		//===============================================
2189		// STYLESHEET TAG e.g. div h1    div p
2190
2191		$this->_set_mergedCSS($oldcascadeCSS[$tag], $p);
2192		// STYLESHEET CLASS e.g. .smallone{}  .redletter{}
2193		foreach ($classes as $class) {
2194			$this->_set_mergedCSS($oldcascadeCSS['CLASS>>' . $class], $p);
2195		}
2196		// STYLESHEET CLASS e.g. #smallone{}  #redletter{}
2197		if (isset($attr['ID'])) {
2198			$this->_set_mergedCSS($oldcascadeCSS['ID>>' . $attr['ID']], $p);
2199		}
2200		// STYLESHEET CLASS e.g. div.smallone{}  p.redletter{}
2201		foreach ($classes as $class) {
2202			$this->_set_mergedCSS($oldcascadeCSS[$tag . '>>CLASS>>' . $class], $p);
2203		}
2204		// STYLESHEET CLASS e.g. div#smallone{}  p#redletter{}
2205		if (isset($attr['ID'])) {
2206			$this->_set_mergedCSS($oldcascadeCSS[$tag . '>>ID>>' . $attr['ID']], $p);
2207		}
2208		//===============================================
2209		// INLINE STYLE e.g. style="CSS:property"
2210		if (isset($attr['STYLE'])) {
2211			$zp = $this->readInlineCSS($attr['STYLE']);
2212			if (is_array($zp)) {
2213				$p = array_merge($p, $zp);
2214			}
2215		}
2216		//===============================================
2217		return $p;
2218	}
2219
2220	// mPDF 5.7.4   nth-child
2221	function _nthchild($f, $c)
2222	{
2223		// $f is formula e.g. 2N+1 split into a preg_match array
2224		// $c is the comparator value e.g row or column number
2225		$c += 1;
2226		$select = false;
2227
2228		$f_count = count($f);
2229		if ($f[0] === 'ODD') {
2230			$a = 2;
2231			$b = 1;
2232		} elseif ($f[0] === 'EVEN') {
2233			$a = 2;
2234			$b = 0;
2235		} elseif ($f_count === 2) {
2236			$a = 0;
2237			$b = $f[1] + 0;
2238		} // e.g. (+6)
2239		elseif ($f_count === 3) {  // e.g. (2N)
2240			if ($f[2] == '') {
2241				$a = 1;
2242			} elseif ($f[2] == '-') {
2243				$a = -1;
2244			} else {
2245				$a = $f[2] + 0;
2246			}
2247			$b = 0;
2248		} elseif ($f_count === 4) {  // e.g. (2N+6)
2249			if ($f[2] == '') {
2250				$a = 1;
2251			} elseif ($f[2] == '-') {
2252				$a = -1;
2253			} else {
2254				$a = $f[2] + 0;
2255			}
2256			$b = $f[3] + 0;
2257		} else {
2258			return false;
2259		}
2260		if ($a > 0) {
2261			if (((($c % $a) - $b) % $a) === 0 && $c >= $b) {
2262				$select = true;
2263			}
2264		} elseif ($a == 0) {
2265			if ($c == $b) {
2266				$select = true;
2267			}
2268		} else {  // if ($a<0)
2269			if (((($c % $a) - $b) % $a) === 0 && $c <= $b) {
2270				$select = true;
2271			}
2272		}
2273		return $select;
2274	}
2275
2276	private function getFileContents($path)
2277	{
2278		// If local file try using local path (? quicker, but also allowed even if allow_url_fopen false)
2279		$wrapperChecker = new StreamWrapperChecker($this->mpdf);
2280		if ($wrapperChecker->hasBlacklistedStreamWrapper($path)) {
2281			throw new \Mpdf\MpdfException('File contains an invalid stream. Only ' . implode(', ', $wrapperChecker->getWhitelistedStreamWrappers()) . ' streams are allowed.');
2282		}
2283
2284		// mPDF 5.7.3
2285		if (strpos($path, '//') === false) {
2286			$path = preg_replace('/\.css\?.*$/', '.css', $path);
2287		}
2288
2289		$contents = @file_get_contents($path);
2290
2291		if ($contents) {
2292			return $contents;
2293		}
2294
2295		if ($this->mpdf->basepathIsLocal) {
2296
2297			$tr = parse_url($path);
2298			$lp = __FILE__;
2299			$ap = realpath($lp);
2300			$ap = str_replace("\\", '/', $ap);
2301			$docroot = substr($ap, 0, strpos($ap, $lp));
2302
2303			// WriteHTML parses all paths to full URLs; may be local file name
2304			// DOCUMENT_ROOT is not returned on IIS
2305			if (!empty($tr['scheme']) && $tr['host'] && !empty($_SERVER['DOCUMENT_ROOT'])) {
2306				$localpath = $_SERVER['DOCUMENT_ROOT'] . $tr['path'];
2307			} elseif ($docroot) {
2308				$localpath = $docroot . $tr['path'];
2309			} else {
2310				$localpath = $path;
2311			}
2312
2313			$contents = @file_get_contents($localpath);
2314
2315		} elseif (!$contents && !ini_get('allow_url_fopen') && function_exists('curl_init')) { // if not use full URL
2316
2317			$ch = curl_init($path);
2318			curl_setopt($ch, CURLOPT_HEADER, 0);
2319			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2320			$contents = curl_exec($ch);
2321			curl_close($ch);
2322
2323		}
2324
2325		return $contents;
2326	}
2327
2328}
2329