1 <?php
2 
3 namespace Mpdf;
4 
5 use Mpdf\Color\ColorConverter;
6 use Mpdf\Css\TextVars;
7 use Mpdf\File\StreamWrapperChecker;
8 use Mpdf\Utils\Arrays;
9 use Mpdf\Utils\UtfString;
10 
11 class 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