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