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