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