1<?php 2 3namespace Mpdf\Image; 4 5use Mpdf\Color\ColorConverter; 6use Mpdf\Css\TextVars; 7use Mpdf\CssManager; 8use Mpdf\Language\LanguageToFontInterface; 9use Mpdf\Language\ScriptToLanguageInterface; 10use Mpdf\Mpdf; 11use Mpdf\Otl; 12use Mpdf\SizeConverter; 13use Mpdf\Ucdn; 14use Mpdf\Utils\Arrays; 15use Mpdf\Utils\UtfString; 16 17/** 18 * SVG class modified for mPDF version >= 6.0 19 * 20 * Works in pixels as main units - converting to PDF units when outputing to PDF string and on returning size 21 * 22 * @author Ian Back 23 * @author sylvain briand (syb@godisaduck.com), modified by rick trevino (rtrevino1@yahoo.com) 24 * 25 * @link http://www.godisaduck.com/svg2pdf_with_fpdf 26 * @link http://rhodopsin.blogspot.com 27 */ 28class Svg 29{ 30 31 /** 32 * ATM marked as public in spite of xml handling callbacks 33 * 34 * @var \Mpdf\Mpdf 35 */ 36 public $mpdf; 37 38 /** 39 * @var \Mpdf\Otl 40 */ 41 public $otl; 42 43 /** 44 * @var \Mpdf\CssManager 45 */ 46 public $cssManager; 47 48 /** 49 * @var \Mpdf\SizeConverter 50 */ 51 public $sizeConverter; 52 53 /** 54 * @var \Mpdf\Color\ColorConverter 55 */ 56 public $colorConverter; 57 58 /** 59 * @var \Mpdf\Language\LanguageToFontInterface 60 */ 61 public $languageToFont; 62 63 /** 64 * @var \Mpdf\Language\ScriptToLanguageInterface 65 */ 66 public $scriptToLanguage; 67 68 /** 69 * @var \Mpdf\Image\ImageProcessor 70 */ 71 private $imageProcessor; 72 73 /** 74 * Holds content of SVG fonts defined in image 75 * 76 * @var array 77 */ 78 var $svg_font; 79 80 /** 81 * contient les infos sur les gradient fill du svg classé par id du svg 82 * 83 * @var array 84 */ 85 var $svg_gradient; 86 87 /** 88 * contient les ids des objet shading 89 * 90 * @var array 91 */ 92 var $svg_shadinglist; 93 94 /** 95 * contenant les infos du svg voulue par l'utilisateur 96 * 97 * @var array 98 */ 99 var $svg_info; 100 101 /** 102 * holds all attributes of root <svg> tag 103 * 104 * @var array 105 */ 106 var $svg_attribs; 107 108 /** 109 * contenant les style de groupes du svg 110 * 111 * @var array 112 */ 113 var $svg_style; 114 115 /** 116 * contenant le tracage du svg en lui même. 117 * 118 * @var string 119 */ 120 var $svg_string; 121 122 /** 123 * holds string info to write txt to image 124 * 125 * @var string 126 */ 127 var $txt_data; 128 129 /** 130 * @var array 131 */ 132 var $txt_style; 133 134 var $xbase; 135 136 var $ybase; 137 138 var $svg_error; 139 140 var $subPathInit; 141 142 var $spxstart; 143 144 var $spystart; 145 146 var $kp; // convert pixels to PDF units 147 148 var $pathBBox; 149 150 var $textlength; // mPDF 5.7.4 151 152 var $texttotallength; // mPDF 5.7.4 153 154 var $textoutput; // mPDF 5.7.4 155 156 var $textanchor; // mPDF 5.7.4 157 158 var $textXorigin; // mPDF 5.7.4 159 160 var $textYorigin; // mPDF 5.7.4 161 162 var $textjuststarted; // mPDF 5.7.4 163 164 var $intext; // mPDF 5.7.4 165 166 private $dashesUsed; 167 168 private $kf; 169 170 private $lastcommand; 171 172 private $lastcontrolpoints; 173 174 private $inDefs; 175 176 public function __construct( 177 Mpdf $mpdf, 178 Otl $otl, 179 CssManager $cssManager, 180 ImageProcessor $imageProcessor, 181 SizeConverter $sizeConverter, 182 ColorConverter $colorConverter, 183 LanguageToFontInterface $languageToFont, 184 ScriptToLanguageInterface $scriptToLanguage 185 ) { 186 187 $this->mpdf = $mpdf; 188 $this->otl = $otl; 189 $this->cssManager = $cssManager; 190 $this->imageProcessor = $imageProcessor; 191 $this->sizeConverter = $sizeConverter; 192 $this->colorConverter = $colorConverter; 193 $this->languageToFont = $languageToFont; 194 $this->scriptToLanguage = $scriptToLanguage; 195 196 $this->svg_font = []; // mPDF 6 197 $this->svg_gradient = []; 198 $this->svg_shadinglist = []; 199 $this->txt_data = []; 200 $this->svg_string = ''; 201 $this->svg_info = []; 202 $this->svg_attribs = []; 203 $this->xbase = 0; 204 $this->ybase = 0; 205 $this->svg_error = false; 206 $this->subPathInit = false; 207 $this->dashesUsed = false; 208 209 $this->textlength = 0; // mPDF 5.7.4 210 $this->texttotallength = 0; // mPDF 5.7.4 211 $this->textoutput = ''; // mPDF 5.7.4 212 $this->textanchor = 'start'; // mPDF 5.7.4 213 $this->textXorigin = 0; // mPDF 5.7.4 214 $this->textYorigin = 0; // mPDF 5.7.4 215 $this->textjuststarted = false; // mPDF 5.7.4 216 $this->intext = false; // mPDF 5.7.4 217 218 $this->kp = 72 / $mpdf->img_dpi; // constant To convert pixels to pts/PDF units 219 $this->kf = 1; // constant To convert font size if re-mapped 220 $this->pathBBox = []; 221 222 $this->svg_style = [ 223 [ 224 'fill' => 'black', 225 'fill-opacity' => 1, // remplissage opaque par defaut 226 'fill-rule' => 'nonzero', // mode de remplissage par defaut 227 'stroke' => 'none', // pas de trait par defaut 228 'stroke-linecap' => 'butt', // style de langle par defaut 229 'stroke-linejoin' => 'miter', 230 'stroke-miterlimit' => 4, // limite de langle par defaut 231 'stroke-opacity' => 1, // trait opaque par defaut 232 'stroke-width' => 1, 233 'stroke-dasharray' => 0, 234 'stroke-dashoffset' => 0, 235 'color' => '' 236 ] 237 ]; 238 239 $this->txt_style = [ 240 [ 241 'fill' => 'black', // pas de remplissage par defaut 242 'font-family' => $mpdf->default_font, 243 'font-size' => $mpdf->default_font_size, // ****** this is pts 244 'font-weight' => 'normal', // normal | bold 245 'font-style' => 'normal', // italic | normal 246 'text-anchor' => 'start', // alignment: start, middle, end 247 'fill-opacity' => 1, // remplissage opaque par defaut 248 'fill-rule' => 'nonzero', // mode de remplissage par defaut 249 'stroke' => 'none', // pas de trait par defaut 250 'stroke-opacity' => 1, // trait opaque par defaut 251 'stroke-width' => 1, 252 'color' => '' 253 ] 254 ]; 255 } 256 257 // mPDF 5.7.4 Embedded image 258 function svgImage($attribs) 259 { 260 // x and y are coordinates 261 $x = (isset($attribs['x']) ? $attribs['x'] : 0); 262 $y = (isset($attribs['y']) ? $attribs['y'] : 0); 263 // preserveAspectRatio 264 $par = (isset($attribs['preserveAspectRatio']) ? $attribs['preserveAspectRatio'] : 'xMidYMid meet'); 265 // width and height are <lengths> - Required attributes 266 $wset = (isset($attribs['width']) ? $attribs['width'] : 0); 267 $hset = (isset($attribs['height']) ? $attribs['height'] : 0); 268 $w = $this->sizeConverter->convert($wset, $this->svg_info['w'] * (25.4 / $this->mpdf->dpi), $this->mpdf->FontSize, false); 269 $h = $this->sizeConverter->convert($hset, $this->svg_info['h'] * (25.4 / $this->mpdf->dpi), $this->mpdf->FontSize, false); 270 if ($w == 0 || $h == 0) { 271 return; 272 } 273 // Convert to pixels = SVG units 274 $w *= 1 / (25.4 / $this->mpdf->dpi); 275 $h *= 1 / (25.4 / $this->mpdf->dpi); 276 277 $srcpath = $attribs['xlink:href']; 278 $orig_srcpath = ''; 279 if (trim($srcpath) != '' && substr($srcpath, 0, 4) == 'var:') { 280 $orig_srcpath = $srcpath; 281 $srcpath = $this->mpdf->GetFullPath($srcpath); 282 } 283 284 // Image file (does not allow vector images i.e. WMF/SVG) 285 // mPDF 6 Added $this->mpdf->interpolateImages 286 $info = $this->imageProcessor->getImage($srcpath, true, false, $orig_srcpath, $this->mpdf->interpolateImages); 287 if (!$info) { 288 return; 289 } 290 291 // x,y,w,h define the reference rectangle 292 $img_h = $h; 293 $img_w = $w; 294 $img_x = $x; 295 $img_y = $y; 296 $meetOrSlice = 'meet'; 297 298 // preserveAspectRatio 299 $ar = preg_split('/\s+/', strtolower($par)); 300 if ($ar[0] != 'none') { // If "none" need to do nothing 301 // Force uniform scaling 302 if (isset($ar[1]) && $ar[1] == 'slice') { 303 $meetOrSlice = 'slice'; 304 } else { 305 $meetOrSlice = 'meet'; 306 } 307 if ($info['h'] / $info['w'] > $h / $w) { 308 if ($meetOrSlice == 'meet') { // the entire viewBox is visible within the viewport 309 $img_w = $img_h * $info['w'] / $info['h']; 310 } else { // the entire viewport is covered by the viewBox 311 $img_h = $img_w * $info['h'] / $info['w']; 312 } 313 } elseif ($info['h'] / $info['w'] < $h / $w) { 314 if ($meetOrSlice == 'meet') { // the entire viewBox is visible within the viewport 315 $img_h = $img_w * $info['h'] / $info['w']; 316 } else { // the entire viewport is covered by the viewBox 317 $img_w = $img_h * $info['w'] / $info['h']; 318 } 319 } 320 if ($ar[0] == 'xminymin') { 321 // do nothing to x 322 // do nothing to y 323 } elseif ($ar[0] == 'xmidymin') { 324 $img_x += $w / 2 - $img_w / 2; // xMid 325 // do nothing to y 326 } elseif ($ar[0] == 'xmaxymin') { 327 $img_x += $w - $img_w; // xMax 328 // do nothing to y 329 } elseif ($ar[0] == 'xminymid') { 330 // do nothing to x 331 $img_y += $h / 2 - $img_h / 2; // yMid 332 } elseif ($ar[0] == 'xmaxymid') { 333 $img_x += $w - $img_w; // xMax 334 $img_y += $h / 2 - $img_h / 2; // yMid 335 } elseif ($ar[0] == 'xminymax') { 336 // do nothing to x 337 $img_y += $h - $img_h; // yMax 338 } elseif ($ar[0] == 'xmidymax') { 339 $img_x += $w / 2 - $img_w / 2; // xMid 340 $img_y += $h - $img_h; // yMax 341 } elseif ($ar[0] == 'xmaxymax') { 342 $img_x += $w - $img_w; // xMax 343 $img_y += $h - $img_h; // yMax 344 } else { // xMidYMid (the default) 345 $img_x += $w / 2 - $img_w / 2; // xMid 346 $img_y += $h / 2 - $img_h / 2; // yMid 347 } 348 } 349 350 // Output 351 if ($meetOrSlice == 'slice') { // need to add a clipping path to reference rectangle 352 $s = ' q 0 w '; // Line width=0 353 $s .= sprintf('%.3F %.3F m ', ($x) * $this->kp, (-($y + $h)) * $this->kp); // start point TL before the arc 354 $s .= sprintf('%.3F %.3F l ', ($x) * $this->kp, (-($y)) * $this->kp); // line to BL 355 $s .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-($y)) * $this->kp); // line to BR 356 $s .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-($y + $h)) * $this->kp); // line to TR 357 $s .= sprintf('%.3F %.3F l ', ($x) * $this->kp, (-($y + $h)) * $this->kp); // line to TL 358 $s .= ' W n '; // Ends path no-op & Sets the clipping path 359 $this->svgWriteString($s); 360 } 361 362 $outstring = sprintf(" q %.3F 0 0 %.3F %.3F %.3F cm /I%d Do Q ", $img_w * $this->kp, $img_h * $this->kp, $img_x * $this->kp, -($img_y + $img_h) * $this->kp, $info['i']); 363 $this->svgWriteString($outstring); 364 365 if ($meetOrSlice == 'slice') { // need to end clipping path 366 $this->svgWriteString(' Q '); 367 } 368 } 369 370 function svgGradient($gradient_info, $attribs, $element) 371 { 372 $n = count($this->mpdf->gradients) + 1; 373 374 // Get bounding dimensions of element 375 $w = 100; 376 $h = 100; 377 $x_offset = 0; 378 $y_offset = 0; 379 if ($element == 'rect') { 380 $w = $attribs['width']; 381 $h = $attribs['height']; 382 $x_offset = $attribs['x']; 383 $y_offset = $attribs['y']; 384 } elseif ($element == 'ellipse') { 385 $w = $attribs['rx'] * 2; 386 $h = $attribs['ry'] * 2; 387 $x_offset = $attribs['cx'] - $attribs['rx']; 388 $y_offset = $attribs['cy'] - $attribs['ry']; 389 } elseif ($element == 'circle') { 390 $w = $attribs['r'] * 2; 391 $h = $attribs['r'] * 2; 392 $x_offset = $attribs['cx'] - $attribs['r']; 393 $y_offset = $attribs['cy'] - $attribs['r']; 394 } elseif ($element == 'polygon') { 395 $pts = preg_split('/[ ,]+/', trim($attribs['points'])); 396 $maxr = $maxb = 0; 397 $minl = $mint = 999999; 398 for ($i = 0; $i < count($pts); $i++) { 399 if ($i % 2 == 0) { // x values 400 $minl = min($minl, $pts[$i]); 401 $maxr = max($maxr, $pts[$i]); 402 } else { // y values 403 $mint = min($mint, $pts[$i]); 404 $maxb = max($maxb, $pts[$i]); 405 } 406 } 407 $w = $maxr - $minl; 408 $h = $maxb - $mint; 409 $x_offset = $minl; 410 $y_offset = $mint; 411 } elseif ($element == 'path') { 412 if (is_array($this->pathBBox) && $this->pathBBox[2] > 0) { 413 $w = $this->pathBBox[2]; 414 $h = $this->pathBBox[3]; 415 $x_offset = $this->pathBBox[0]; 416 $y_offset = $this->pathBBox[1]; 417 } else { 418 preg_match_all('/([a-z]|[A-Z])([ ,\-.\d]+)*/', $attribs['d'], $commands, PREG_SET_ORDER); 419 $maxr = $maxb = 0; 420 $minl = $mint = 999999; 421 foreach ($commands as $c) { 422 if (count($c) == 3) { 423 list($tmp, $cmd, $arg) = $c; 424 if ($cmd == 'M' || $cmd == 'L' || $cmd == 'C' || $cmd == 'S' || $cmd == 'Q' || $cmd == 'T') { 425 $pts = preg_split('/[ ,]+/', trim($arg)); 426 for ($i = 0; $i < count($pts); $i++) { 427 if ($i % 2 == 0) { // x values 428 $minl = min($minl, $pts[$i]); 429 $maxr = max($maxr, $pts[$i]); 430 } else { // y values 431 $mint = min($mint, $pts[$i]); 432 $maxb = max($maxb, $pts[$i]); 433 } 434 } 435 } 436 if ($cmd == 'H') { // sets new x 437 $minl = min($minl, $arg); 438 $maxr = max($maxr, $arg); 439 } 440 if ($cmd == 'V') { // sets new y 441 $mint = min($mint, $arg); 442 $maxb = max($maxb, $arg); 443 } 444 } 445 } 446 $w = $maxr - $minl; 447 $h = $maxb - $mint; 448 $x_offset = $minl; 449 $y_offset = $mint; 450 } 451 } 452 if (!$w || $w == -999999) { 453 $w = 100; 454 } 455 if (!$h || $h == -999999) { 456 $h = 100; 457 } 458 if ($x_offset == 999999) { 459 $x_offset = 0; 460 } 461 if ($y_offset == 999999) { 462 $y_offset = 0; 463 } 464 465 // TRANSFORMATIONS 466 $transformations = ''; 467 if (isset($gradient_info['transform'])) { 468 preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)\((.*?)\)/is', $gradient_info['transform'], $m); 469 if (count($m[0])) { 470 for ($i = 0; $i < count($m[0]); $i++) { 471 $c = strtolower($m[1][$i]); 472 $v = trim($m[2][$i]); 473 $vv = preg_split('/[ ,]+/', $v); 474 if ($c == 'matrix' && count($vv) == 6) { 475 // Note angle of rotation is reversed (from SVG to PDF), so vv[1] and vv[2] are negated 476 // cf svgDefineStyle() 477 $transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $vv[0], -$vv[1], -$vv[2], $vv[3], $vv[4] * $this->kp, -$vv[5] * $this->kp); 478 } elseif ($c == 'translate' && count($vv)) { 479 $tm[4] = $vv[0]; 480 if (count($vv) == 2) { 481 $t_y = -$vv[1]; 482 } else { 483 $t_y = 0; 484 } 485 $tm[5] = $t_y; 486 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $tm[4] * $this->kp, $tm[5] * $this->kp); 487 } elseif ($c == 'scale' && count($vv)) { 488 if (count($vv) == 2) { 489 $s_y = $vv[1]; 490 } else { 491 $s_y = $vv[0]; 492 } 493 $tm[0] = $vv[0]; 494 $tm[3] = $s_y; 495 $transformations .= sprintf(' %.3F 0 0 %.3F 0 0 cm ', $tm[0], $tm[3]); 496 } elseif ($c == 'rotate' && count($vv)) { 497 $tm[0] = cos(deg2rad(-$vv[0])); 498 $tm[1] = sin(deg2rad(-$vv[0])); 499 $tm[2] = -$tm[1]; 500 $tm[3] = $tm[0]; 501 if (count($vv) == 3) { 502 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $vv[1] * $this->kp, -$vv[2] * $this->kp); 503 } 504 $transformations .= sprintf(' %.3F %.3F %.3F %.3F 0 0 cm ', $tm[0], $tm[1], $tm[2], $tm[3]); 505 if (count($vv) == 3) { 506 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', -$vv[1] * $this->kp, $vv[2] * $this->kp); 507 } 508 } elseif ($c == 'skewx' && count($vv)) { 509 $tm[2] = tan(deg2rad(-$vv[0])); 510 $transformations .= sprintf(' 1 0 %.3F 1 0 0 cm ', $tm[2]); 511 } elseif ($c == 'skewy' && count($vv)) { 512 $tm[1] = tan(deg2rad(-$vv[0])); 513 $transformations .= sprintf(' 1 %.3F 0 1 0 0 cm ', $tm[1]); 514 } 515 } 516 } 517 } 518 519 520 $return = ""; 521 522 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') { 523 if ($transformations) { 524 $return .= $transformations; 525 } 526 } 527 $spread = 'P'; // pad 528 if (isset($gradient_info['spread'])) { 529 if (strtolower($gradient_info['spread']) == 'reflect') { 530 $spread = 'F'; 531 } // reflect 532 elseif (strtolower($gradient_info['spread']) == 'repeat') { 533 $spread = 'R'; 534 } // repeat 535 } 536 537 538 for ($i = 0; $i < (count($gradient_info['color'])); $i++) { 539 if (stristr($gradient_info['color'][$i]['offset'], '%') !== false) { 540 $gradient_info['color'][$i]['offset'] = ((float) $gradient_info['color'][$i]['offset']) / 100; 541 } 542 if (isset($gradient_info['color'][($i + 1)]['offset']) && stristr($gradient_info['color'][($i + 1)]['offset'], '%') !== false) { 543 $gradient_info['color'][($i + 1)]['offset'] = ((float) $gradient_info['color'][($i + 1)]['offset']) / 100; 544 } 545 if ($gradient_info['color'][$i]['offset'] < 0) { 546 $gradient_info['color'][$i]['offset'] = 0; 547 } 548 if ($gradient_info['color'][$i]['offset'] > 1) { 549 $gradient_info['color'][$i]['offset'] = 1; 550 } 551 if ($i > 0) { 552 if ($gradient_info['color'][$i]['offset'] < $gradient_info['color'][($i - 1)]['offset']) { 553 $gradient_info['color'][$i]['offset'] = $gradient_info['color'][($i - 1)]['offset']; 554 } 555 } 556 } 557 558 if (isset($gradient_info['color'][0]['offset']) && $gradient_info['color'][0]['offset'] > 0) { 559 array_unshift($gradient_info['color'], $gradient_info['color'][0]); 560 $gradient_info['color'][0]['offset'] = 0; 561 } 562 $ns = count($gradient_info['color']); 563 if (isset($gradient_info['color'][($ns - 1)]['offset']) && $gradient_info['color'][($ns - 1)]['offset'] < 1) { 564 $gradient_info['color'][] = $gradient_info['color'][($ns - 1)]; 565 $gradient_info['color'][($ns)]['offset'] = 1; 566 } 567 $ns = count($gradient_info['color']); 568 569 570 571 572 if ($gradient_info['type'] == 'linear') { 573 // mPDF 4.4.003 574 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') { 575 if (isset($gradient_info['info']['x1'])) { 576 $gradient_info['info']['x1'] = ($gradient_info['info']['x1'] - $x_offset) / $w; 577 } 578 if (isset($gradient_info['info']['y1'])) { 579 $gradient_info['info']['y1'] = ($gradient_info['info']['y1'] - $y_offset) / $h; 580 } 581 if (isset($gradient_info['info']['x2'])) { 582 $gradient_info['info']['x2'] = ($gradient_info['info']['x2'] - $x_offset) / $w; 583 } 584 if (isset($gradient_info['info']['y2'])) { 585 $gradient_info['info']['y2'] = ($gradient_info['info']['y2'] - $y_offset) / $h; 586 } 587 } 588 if (isset($gradient_info['info']['x1'])) { 589 $x1 = $gradient_info['info']['x1']; 590 } else { 591 $x1 = 0; 592 } 593 if (isset($gradient_info['info']['y1'])) { 594 $y1 = $gradient_info['info']['y1']; 595 } else { 596 $y1 = 0; 597 } 598 if (isset($gradient_info['info']['x2'])) { 599 $x2 = $gradient_info['info']['x2']; 600 } else { 601 $x2 = 1; 602 } 603 if (isset($gradient_info['info']['y2'])) { 604 $y2 = $gradient_info['info']['y2']; 605 } else { 606 $y2 = 0; 607 } // mPDF 6 608 609 if (strpos($x1, '%') !== false) { 610 $x1 = (stristr($x1, '%', true) + 0) / 100; 611 } 612 if (strpos($x2, '%') !== false) { 613 $x2 = (stristr($x2, '%', true) + 0) / 100; 614 } 615 if (strpos($y1, '%') !== false) { 616 $y1 = (stristr($y1, '%', true) + 0) / 100; 617 } 618 if (strpos($y2, '%') !== false) { 619 $y2 = (stristr($y2, '%', true) + 0) / 100; 620 } 621 622 // mPDF 5.0.042 623 $bboxw = $w; 624 $bboxh = $h; 625 $usex = $x_offset; 626 $usey = $y_offset; 627 $usew = $bboxw; 628 $useh = $bboxh; 629 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') { 630 $angle = rad2deg(atan2(($gradient_info['info']['y2'] - $gradient_info['info']['y1']), ($gradient_info['info']['x2'] - $gradient_info['info']['x1']))); 631 if ($angle < 0) { 632 $angle += 360; 633 } elseif ($angle > 360) { 634 $angle -= 360; 635 } 636 if ($angle != 0 && $angle != 360 && $angle != 90 && $angle != 180 && $angle != 270) { 637 if ($w >= $h) { 638 $y1 *= $h / $w; 639 $y2 *= $h / $w; 640 $usew = $useh = $bboxw; 641 } else { 642 $x1 *= $w / $h; 643 $x2 *= $w / $h; 644 $usew = $useh = $bboxh; 645 } 646 } 647 } 648 $a = $usew; // width 649 $d = -$useh; // height 650 $e = $usex; // x- offset 651 $f = -$usey; // -y-offset 652 653 $return .= sprintf('%.3F 0 0 %.3F %.3F %.3F cm ', $a * $this->kp, $d * $this->kp, $e * $this->kp, $f * $this->kp); 654 655 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'objectboundingbox') { 656 if ($transformations) { 657 $return .= $transformations; 658 } 659 } 660 661 $trans = false; 662 663 if ($spread == 'R' || $spread == 'F') { // Repeat / Reflect 664 665 $offs = []; 666 for ($i = 0; $i < $ns; $i++) { 667 $offs[$i] = $gradient_info['color'][$i]['offset']; 668 } 669 670 $gp = 0; 671 $inside = true; 672 673 while ($inside) { 674 $gp++; 675 for ($i = 0; $i < $ns; $i++) { 676 if ($spread == 'F' && ($gp % 2) == 1) { // Reflect 677 $gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][(($ns * ($gp - 1)) + ($ns - $i - 1))]; 678 $tmp = $gp + (1 - $offs[($ns - $i - 1)]); 679 $gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp; 680 } else { // Reflect 681 $gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][$i]; 682 $tmp = $gp + $offs[$i]; 683 $gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp; 684 } 685 // IF STILL INSIDE BOX OR STILL VALID 686 // Point on axis to test 687 $px1 = $x1 + ($x2 - $x1) * $tmp; 688 $py1 = $y1 + ($y2 - $y1) * $tmp; 689 // Get perpendicular axis 690 $alpha = atan2($y2 - $y1, $x2 - $x1); 691 $alpha += M_PI / 2; // rotate 90 degrees 692 // Get arbitrary point to define line perpendicular to axis 693 $px2 = $px1 + cos($alpha); 694 $py2 = $py1 + sin($alpha); 695 696 $res1 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 0, 1); // $x=0 vert axis 697 $res2 = $this->testIntersect($px1, $py1, $px2, $py2, 1, 0, 1, 1); // $x=1 vert axis 698 $res3 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 1, 0); // $y=0 horiz axis 699 $res4 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 1, 1, 1); // $y=1 horiz axis 700 701 if (!$res1 && !$res2 && !$res3 && !$res4) { 702 $inside = false; 703 } 704 } 705 } 706 707 $inside = true; 708 $gp = 0; 709 while ($inside) { 710 $gp++; 711 $newarr = []; 712 for ($i = 0; $i < $ns; $i++) { 713 if ($spread == 'F') { // Reflect 714 $newarr[$i] = $gradient_info['color'][($ns - $i - 1)]; 715 if (($gp % 2) == 1) { 716 $tmp = -$gp + (1 - $offs[($ns - $i - 1)]); 717 $newarr[$i]['offset'] = $tmp; 718 } else { 719 $tmp = -$gp + $offs[$i]; 720 $newarr[$i]['offset'] = $tmp; 721 } 722 } else { // Reflect 723 $newarr[$i] = $gradient_info['color'][$i]; 724 $tmp = -$gp + $offs[$i]; 725 $newarr[$i]['offset'] = $tmp; 726 } 727 728 // IF STILL INSIDE BOX OR STILL VALID 729 // Point on axis to test 730 $px1 = $x1 + ($x2 - $x1) * $tmp; 731 $py1 = $y1 + ($y2 - $y1) * $tmp; 732 // Get perpendicular axis 733 $alpha = atan2($y2 - $y1, $x2 - $x1); 734 $alpha += M_PI / 2; // rotate 90 degrees 735 // Get arbitrary point to define line perpendicular to axis 736 $px2 = $px1 + cos($alpha); 737 $py2 = $py1 + sin($alpha); 738 739 $res1 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 0, 1); // $x=0 vert axis 740 $res2 = $this->testIntersect($px1, $py1, $px2, $py2, 1, 0, 1, 1); // $x=1 vert axis 741 $res3 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 0, 1, 0); // $y=0 horiz axis 742 $res4 = $this->testIntersect($px1, $py1, $px2, $py2, 0, 1, 1, 1); // $y=1 horiz axis 743 if (!$res1 && !$res2 && !$res3 && !$res4) { 744 $inside = false; 745 } 746 } 747 for ($i = ($ns - 1); $i >= 0; $i--) { 748 if (isset($newarr[$i]['offset'])) { 749 array_unshift($gradient_info['color'], $newarr[$i]); 750 } 751 } 752 } 753 } 754 755 // Gradient STOPs 756 $stops = count($gradient_info['color']); 757 if ($stops < 2) { 758 return ''; 759 } 760 761 $range = $gradient_info['color'][count($gradient_info['color']) - 1]['offset'] - $gradient_info['color'][0]['offset']; 762 $min = $gradient_info['color'][0]['offset']; 763 764 for ($i = 0; $i < ($stops); $i++) { 765 if (!$gradient_info['color'][$i]['color']) { 766 if ($gradient_info['colorspace'] == 'RGB') { 767 $gradient_info['color'][$i]['color'] = '0 0 0'; 768 } elseif ($gradient_info['colorspace'] == 'Gray') { 769 $gradient_info['color'][$i]['color'] = '0'; 770 } elseif ($gradient_info['colorspace'] == 'CMYK') { 771 $gradient_info['color'][$i]['color'] = '1 1 1 1'; 772 } 773 } 774 $offset = ($gradient_info['color'][$i]['offset'] - $min) / $range; 775 $this->mpdf->gradients[$n]['stops'][] = [ 776 'col' => $gradient_info['color'][$i]['color'], 777 'opacity' => $gradient_info['color'][$i]['opacity'], 778 'offset' => $offset]; 779 if ($gradient_info['color'][$i]['opacity'] < 1) { 780 $trans = true; 781 } 782 } 783 $grx1 = $x1 + ($x2 - $x1) * $gradient_info['color'][0]['offset']; 784 $gry1 = $y1 + ($y2 - $y1) * $gradient_info['color'][0]['offset']; 785 $grx2 = $x1 + ($x2 - $x1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset']; 786 $gry2 = $y1 + ($y2 - $y1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset']; 787 788 $this->mpdf->gradients[$n]['coords'] = [$grx1, $gry1, $grx2, $gry2]; 789 790 $this->mpdf->gradients[$n]['colorspace'] = $gradient_info['colorspace']; 791 792 $this->mpdf->gradients[$n]['type'] = 2; 793 $this->mpdf->gradients[$n]['fo'] = true; 794 795 $this->mpdf->gradients[$n]['extend'] = ['true', 'true']; 796 if ($trans) { 797 $this->mpdf->gradients[$n]['trans'] = true; 798 $return .= ' /TGS' . ($n) . ' gs '; 799 } 800 $return .= ' /Sh' . ($n) . ' sh '; 801 $return .= " Q\n"; 802 } elseif ($gradient_info['type'] == 'radial') { 803 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') { 804 if ($w > $h) { 805 $h = $w; 806 } else { 807 $w = $h; 808 } 809 if (isset($gradient_info['info']['x0'])) { 810 $gradient_info['info']['x0'] = ($gradient_info['info']['x0'] - $x_offset) / $w; 811 } 812 if (isset($gradient_info['info']['y0'])) { 813 $gradient_info['info']['y0'] = ($gradient_info['info']['y0'] - $y_offset) / $h; 814 } 815 if (isset($gradient_info['info']['x1'])) { 816 $gradient_info['info']['x1'] = ($gradient_info['info']['x1'] - $x_offset) / $w; 817 } 818 if (isset($gradient_info['info']['y1'])) { 819 $gradient_info['info']['y1'] = ($gradient_info['info']['y1'] - $y_offset) / $h; 820 } 821 if (isset($gradient_info['info']['r'])) { 822 $gradient_info['info']['rx'] = $gradient_info['info']['r'] / $w; 823 } 824 if (isset($gradient_info['info']['r'])) { 825 $gradient_info['info']['ry'] = $gradient_info['info']['r'] / $h; 826 } 827 } 828 829 if (isset($gradient_info['info']['x0'])) { 830 $x0 = $gradient_info['info']['x0']; 831 } else { 832 $x0 = 0.5; 833 } 834 if (isset($gradient_info['info']['y0'])) { 835 $y0 = $gradient_info['info']['y0']; 836 } else { 837 $y0 = 0.5; 838 } 839 if (isset($gradient_info['info']['rx'])) { 840 $rx = $gradient_info['info']['rx']; 841 } elseif (isset($gradient_info['info']['r'])) { 842 $rx = $gradient_info['info']['r']; 843 } else { 844 $rx = 0.5; 845 } 846 if (isset($gradient_info['info']['ry'])) { 847 $ry = $gradient_info['info']['ry']; 848 } elseif (isset($gradient_info['info']['r'])) { 849 $ry = $gradient_info['info']['r']; 850 } else { 851 $ry = 0.5; 852 } 853 if (isset($gradient_info['info']['x1'])) { 854 $x1 = $gradient_info['info']['x1']; 855 } else { 856 $x1 = $x0; 857 } 858 if (isset($gradient_info['info']['y1'])) { 859 $y1 = $gradient_info['info']['y1']; 860 } else { 861 $y1 = $y0; 862 } 863 864 if (strpos($x1, '%') !== false) { 865 $x1 = (stristr($x1, '%', true) + 0) / 100; 866 } 867 if (strpos($x0, '%') !== false) { 868 $x0 = (stristr($x0, '%', true) + 0) / 100; 869 } 870 if (strpos($y1, '%') !== false) { 871 $y1 = (stristr($y1, '%', true) + 0) / 100; 872 } 873 if (strpos($y0, '%') !== false) { 874 $y0 = (stristr($y0, '%', true) + 0) / 100; 875 } 876 if (strpos($rx, '%') !== false) { 877 $rx = (stristr($rx, '%', true) + 0) / 100; 878 } 879 if (strpos($ry, '%') !== false) { 880 $ry = (stristr($ry, '%', true) + 0) / 100; 881 } 882 883 $bboxw = $w; 884 $bboxh = $h; 885 $usex = $x_offset; 886 $usey = $y_offset; 887 $usew = $bboxw; 888 $useh = $bboxh; 889 890 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'userspaceonuse') { 891 892 $ay1 = isset($gradient_info['info']['y1']) ? $gradient_info['info']['y1'] : 0; 893 $ax1 = isset($gradient_info['info']['x1']) ? $gradient_info['info']['x1'] : 0; 894 895 $angle = rad2deg(atan2( 896 ($gradient_info['info']['y0'] - $ay1), 897 ($gradient_info['info']['x0'] - $ax1) 898 )); 899 900 if ($angle < 0) { 901 $angle += 360; 902 } elseif ($angle > 360) { 903 $angle -= 360; 904 } 905 906 if ($angle != 0 && $angle != 360 && $angle != 90 && $angle != 180 && $angle != 270) { 907 if ($w >= $h) { 908 $y1 *= $h / $w; 909 $y0 *= $h / $w; 910 $rx *= $h / $w; 911 $ry *= $h / $w; 912 $usew = $useh = $bboxw; 913 } else { 914 $x1 *= $w / $h; 915 $x0 *= $w / $h; 916 $rx *= $w / $h; 917 $ry *= $w / $h; 918 $usew = $useh = $bboxh; 919 } 920 } 921 } 922 923 $a = $usew; // width 924 $d = -$useh; // height 925 $e = $usex; // x- offset 926 $f = -$usey; // -y-offset 927 928 $r = $rx; 929 930 $return .= sprintf('%.3F 0 0 %.3F %.3F %.3F cm ', $a * $this->kp, $d * $this->kp, $e * $this->kp, $f * $this->kp); 931 932 // mPDF 5.0.039 933 if (isset($gradient_info['units']) && strtolower($gradient_info['units']) == 'objectboundingbox') { 934 if ($transformations) { 935 $return .= $transformations; 936 } 937 } 938 939 // mPDF 5.7.4 940 // x1 and y1 (fx, fy) should be inside the circle defined by x0 y0 (cx, cy) 941 // "If the point defined by fx and fy lies outside the circle defined by cx, cy and r, then the user agent shall set 942 // the focal point to the intersection of the line from (cx, cy) to (fx, fy) with the circle defined by cx, cy and r." 943 while (pow(($x1 - $x0), 2) + pow(($y1 - $y0), 2) >= pow($r, 2)) { 944 // Gradually move along fx,fy towards cx,cy in 100'ths until meets criteria 945 $x1 -= ($x1 - $x0) / 100; 946 $y1 -= ($y1 - $y0) / 100; 947 } 948 949 950 if ($spread == 'R' || $spread == 'F') { // Repeat / Reflect 951 $offs = []; 952 for ($i = 0; $i < $ns; $i++) { 953 $offs[$i] = $gradient_info['color'][$i]['offset']; 954 } 955 $gp = 0; 956 $inside = true; 957 while ($inside) { 958 $gp++; 959 for ($i = 0; $i < $ns; $i++) { 960 if ($spread == 'F' && ($gp % 2) == 1) { // Reflect 961 $gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][(($ns * ($gp - 1)) + ($ns - $i - 1))]; 962 $tmp = $gp + (1 - $offs[($ns - $i - 1)]); 963 $gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp; 964 } else { // Reflect 965 $gradient_info['color'][(($ns * $gp) + $i)] = $gradient_info['color'][$i]; 966 $tmp = $gp + $offs[$i]; 967 $gradient_info['color'][(($ns * $gp) + $i)]['offset'] = $tmp; 968 } 969 // IF STILL INSIDE BOX OR STILL VALID 970 // TEST IF circle (perimeter) intersects with 971 // or is enclosed 972 // Point on axis to test 973 $px = $x1 + ($x0 - $x1) * $tmp; 974 $py = $y1 + ($y0 - $y1) * $tmp; 975 $pr = $r * $tmp; 976 $res = $this->testIntersectCircle($px, $py, $pr); 977 if (!$res) { 978 $inside = false; 979 } 980 } 981 } 982 } 983 984 // Gradient STOPs 985 $stops = count($gradient_info['color']); 986 if ($stops < 2) { 987 return ''; 988 } 989 990 $range = $gradient_info['color'][count($gradient_info['color']) - 1]['offset'] - $gradient_info['color'][0]['offset']; 991 $min = $gradient_info['color'][0]['offset']; 992 993 for ($i = 0; $i < ($stops); $i++) { 994 if (!$gradient_info['color'][$i]['color']) { 995 if ($gradient_info['colorspace'] == 'RGB') { 996 $gradient_info['color'][$i]['color'] = '0 0 0'; 997 } elseif ($gradient_info['colorspace'] == 'Gray') { 998 $gradient_info['color'][$i]['color'] = '0'; 999 } elseif ($gradient_info['colorspace'] == 'CMYK') { 1000 $gradient_info['color'][$i]['color'] = '1 1 1 1'; 1001 } 1002 } 1003 $offset = ($gradient_info['color'][$i]['offset'] - $min) / $range; 1004 $this->mpdf->gradients[$n]['stops'][] = [ 1005 'col' => $gradient_info['color'][$i]['color'], 1006 'opacity' => $gradient_info['color'][$i]['opacity'], 1007 'offset' => $offset]; 1008 if ($gradient_info['color'][$i]['opacity'] < 1) { 1009 $trans = true; 1010 } 1011 } 1012 $grx1 = $x1 + ($x0 - $x1) * $gradient_info['color'][0]['offset']; 1013 $gry1 = $y1 + ($y0 - $y1) * $gradient_info['color'][0]['offset']; 1014 $grx2 = $x1 + ($x0 - $x1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset']; 1015 $gry2 = $y1 + ($y0 - $y1) * $gradient_info['color'][count($gradient_info['color']) - 1]['offset']; 1016 $grir = $r * $gradient_info['color'][0]['offset']; 1017 $grr = $r * $gradient_info['color'][count($gradient_info['color']) - 1]['offset']; 1018 1019 $this->mpdf->gradients[$n]['coords'] = [$grx1, $gry1, $grx2, $gry2, abs($grr), abs($grir)]; 1020 1021 $this->mpdf->gradients[$n]['colorspace'] = $gradient_info['colorspace']; 1022 1023 $this->mpdf->gradients[$n]['type'] = 3; 1024 $this->mpdf->gradients[$n]['fo'] = true; 1025 1026 $this->mpdf->gradients[$n]['extend'] = ['true', 'true']; 1027 if (isset($trans) && $trans) { 1028 $this->mpdf->gradients[$n]['trans'] = true; 1029 $return .= ' /TGS' . ($n) . ' gs '; 1030 } 1031 $return .= ' /Sh' . ($n) . ' sh '; 1032 $return .= " Q\n"; 1033 } 1034 1035 return $return; 1036 } 1037 1038 function svgOffset($attribs) 1039 { 1040 // save all <svg> tag attributes 1041 $this->svg_attribs = $attribs; 1042 if (isset($this->svg_attribs['viewBox'])) { 1043 $vb = preg_split('/\s+/is', trim($this->svg_attribs['viewBox'])); 1044 if (count($vb) == 4) { 1045 $this->svg_info['x'] = $vb[0]; 1046 $this->svg_info['y'] = $vb[1]; 1047 $this->svg_info['w'] = $vb[2]; 1048 $this->svg_info['h'] = $vb[3]; 1049// return; 1050 } 1051 } 1052 $svg_w = 0; 1053 $svg_h = 0; 1054 if (isset($attribs['width']) && $attribs['width']) { 1055 $svg_w = $this->sizeConverter->convert($attribs['width']); // mm (interprets numbers as pixels) 1056 } 1057 if (isset($attribs['height']) && $attribs['height']) { 1058 $svg_h = $this->sizeConverter->convert($attribs['height']); // mm 1059 } 1060 1061 1062///* 1063 // mPDF 5.0.005 1064 if (isset($this->svg_info['w']) && $this->svg_info['w']) { // if 'w' set by viewBox 1065 if ($svg_w) { // if width also set, use these values to determine to set size of "pixel" 1066 $this->kp *= ($svg_w / 0.2645) / $this->svg_info['w']; 1067 $this->kf = ($svg_w / 0.2645) / $this->svg_info['w']; 1068 } elseif ($svg_h) { 1069 $this->kp *= ($svg_h / 0.2645) / $this->svg_info['h']; 1070 $this->kf = ($svg_h / 0.2645) / $this->svg_info['h']; 1071 } 1072 return; 1073 } 1074//*/ 1075 // Added to handle file without height or width specified 1076 if (!$svg_w && !$svg_h) { 1077 $svg_w = $svg_h = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; 1078 } // DEFAULT 1079 if (!$svg_w) { 1080 $svg_w = $svg_h; 1081 } 1082 if (!$svg_h) { 1083 $svg_h = $svg_w; 1084 } 1085 1086 $this->svg_info['x'] = 0; 1087 $this->svg_info['y'] = 0; 1088 $this->svg_info['w'] = $svg_w / 0.2645; // mm->pixels 1089 $this->svg_info['h'] = $svg_h / 0.2645; // mm->pixels 1090 } 1091 1092 // 1093 // check if points are within svg, if not, set to max 1094 function svg_overflow($x, $y) 1095 { 1096 $x2 = $x; 1097 $y2 = $y; 1098 if (isset($this->svg_attribs['overflow'])) { 1099 if ($this->svg_attribs['overflow'] == 'hidden') { 1100 // Not sure if this is supposed to strip off units, but since I dont use any I will omlt this step 1101 $svg_w = preg_replace("/([0-9\.]*)(.*)/i", "$1", $this->svg_attribs['width']); 1102 $svg_h = preg_replace("/([0-9\.]*)(.*)/i", "$1", $this->svg_attribs['height']); 1103 1104 // $xmax = floor($this->svg_attribs['width']); 1105 $xmax = floor($svg_w); 1106 $xmin = 0; 1107 // $ymax = floor(($this->svg_attribs['height'] * -1)); 1108 $ymax = floor(($svg_h * -1)); 1109 $ymin = 0; 1110 1111 if ($x > $xmax) { 1112 $x2 = $xmax; // right edge 1113 } 1114 if ($x < $xmin) { 1115 $x2 = $xmin; // left edge 1116 } 1117 if ($y < $ymax) { 1118 $y2 = $ymax; // bottom 1119 } 1120 if ($y > $ymin) { 1121 $y2 = $ymin; // top 1122 } 1123 } 1124 } 1125 1126 1127 return ['x' => $x2, 'y' => $y2]; 1128 } 1129 1130 function svgDefineStyle($critere_style) 1131 { 1132 1133 $tmp = count($this->svg_style) - 1; 1134 $current_style = $this->svg_style[$tmp]; 1135 1136 unset($current_style['transformations']); 1137 1138 // TRANSFORM SCALE 1139 $transformations = ''; 1140 if (isset($critere_style['transform'])) { 1141 preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)\((.*?)\)/is', $critere_style['transform'], $m); 1142 if (count($m[0])) { 1143 for ($i = 0; $i < count($m[0]); $i++) { 1144 $c = strtolower($m[1][$i]); 1145 $v = trim($m[2][$i]); 1146 $vv = preg_split('/[ ,]+/', $v); 1147 if ($c == 'matrix' && count($vv) == 6) { 1148 // mPDF 5.0.039 1149 // Note angle of rotation is reversed (from SVG to PDF), so vv[1] and vv[2] are negated 1150 $transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $vv[0], -$vv[1], -$vv[2], $vv[3], $vv[4] * $this->kp, -$vv[5] * $this->kp); 1151 1152 /* 1153 // The long way of doing this?? 1154 // need to reverse angle of rotation from SVG to PDF 1155 $sx=sqrt(pow($vv[0],2)+pow($vv[2],2)); 1156 if ($vv[0] < 0) { $sx *= -1; } // change sign 1157 $sy=sqrt(pow($vv[1],2)+pow($vv[3],2)); 1158 if ($vv[3] < 0) { $sy *= -1; } // change sign 1159 1160 // rotation angle is 1161 $t=atan2($vv[1],$vv[3]); 1162 $t=atan2(-$vv[2],$vv[0]); // Should be the same value or skew has been applied 1163 1164 // Reverse angle 1165 $t *= -1; 1166 1167 // Rebuild matrix 1168 $ma = $sx * cos($t); 1169 $mb = $sy * sin($t); 1170 $mc = -$sx * sin($t); 1171 $md = $sy * cos($t); 1172 1173 // $transformations .= sprintf(' %.3F %.3F %.3F %.3F %.3F %.3F cm ', $ma, $mb, $mc, $md, $vv[4]*$this->kp, -$vv[5]*$this->kp); 1174 */ 1175 } elseif ($c == 'translate' && count($vv)) { 1176 $tm[4] = $vv[0]; 1177 if (count($vv) == 2) { 1178 $t_y = -$vv[1]; 1179 } else { 1180 $t_y = 0; 1181 } 1182 $tm[5] = $t_y; 1183 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $tm[4] * $this->kp, $tm[5] * $this->kp); 1184 } elseif ($c == 'scale' && count($vv)) { 1185 if (count($vv) == 2) { 1186 $s_y = $vv[1]; 1187 } else { 1188 $s_y = $vv[0]; 1189 } 1190 $tm[0] = $vv[0]; 1191 $tm[3] = $s_y; 1192 $transformations .= sprintf(' %.3F 0 0 %.3F 0 0 cm ', $tm[0], $tm[3]); 1193 } elseif ($c == 'rotate' && count($vv)) { 1194 $tm[0] = cos(deg2rad(-$vv[0])); 1195 $tm[1] = sin(deg2rad(-$vv[0])); 1196 $tm[2] = -$tm[1]; 1197 $tm[3] = $tm[0]; 1198 if (count($vv) == 3) { 1199 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', $vv[1] * $this->kp, -$vv[2] * $this->kp); 1200 } 1201 $transformations .= sprintf(' %.3F %.3F %.3F %.3F 0 0 cm ', $tm[0], $tm[1], $tm[2], $tm[3]); 1202 if (count($vv) == 3) { 1203 $transformations .= sprintf(' 1 0 0 1 %.3F %.3F cm ', -$vv[1] * $this->kp, $vv[2] * $this->kp); 1204 } 1205 } elseif ($c == 'skewx' && count($vv)) { 1206 $tm[2] = tan(deg2rad(-$vv[0])); 1207 $transformations .= sprintf(' 1 0 %.3F 1 0 0 cm ', $tm[2]); 1208 } elseif ($c == 'skewy' && count($vv)) { 1209 $tm[1] = tan(deg2rad(-$vv[0])); 1210 $transformations .= sprintf(' 1 %.3F 0 1 0 0 cm ', $tm[1]); 1211 } 1212 } 1213 } 1214 $current_style['transformations'] = $transformations; 1215 } 1216 1217 if (isset($critere_style['style'])) { 1218 if (preg_match('/fill:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/i', $critere_style['style'], $m)) { // mPDF 5.7.2 1219 $current_style['fill'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT); 1220 } else { 1221 $tmp = preg_replace("/(.*)fill:\s*([a-z0-9#_()]*|none)(.*)/i", "$2", $critere_style['style']); 1222 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1223 $current_style['fill'] = $tmp; 1224 } 1225 } 1226 1227 // mPDF 5.7.2 1228 if ((preg_match("/[^-]opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m) || 1229 preg_match("/^opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m)) && $m[1] != 'inherit') { 1230 $current_style['fill-opacity'] = $m[1]; 1231 $current_style['stroke-opacity'] = $m[1]; 1232 } 1233 1234 $tmp = preg_replace("/(.*)fill-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 1235 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1236 $current_style['fill-opacity'] = $tmp; 1237 } 1238 1239 $tmp = preg_replace("/(.*)fill-rule:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 1240 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1241 $current_style['fill-rule'] = $tmp; 1242 } 1243 1244 if (preg_match('/stroke:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) { 1245 $current_style['stroke'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT); 1246 } else { 1247 $tmp = preg_replace("/(.*)stroke:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 1248 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1249 $current_style['stroke'] = $tmp; 1250 } 1251 } 1252 1253 $tmp = preg_replace("/(.*)stroke-linecap:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 1254 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1255 $current_style['stroke-linecap'] = $tmp; 1256 } 1257 1258 $tmp = preg_replace("/(.*)stroke-linejoin:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 1259 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1260 $current_style['stroke-linejoin'] = $tmp; 1261 } 1262 1263 $tmp = preg_replace("/(.*)stroke-miterlimit:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 1264 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1265 $current_style['stroke-miterlimit'] = $tmp; 1266 } 1267 1268 $tmp = preg_replace("/(.*)stroke-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 1269 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1270 $current_style['stroke-opacity'] = $tmp; 1271 } 1272 1273 $tmp = preg_replace("/(.*)stroke-width:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 1274 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1275 $current_style['stroke-width'] = $tmp; 1276 } 1277 1278 $tmp = preg_replace("/(.*)stroke-dasharray:\s*([a-z0-9., ]*|none)(.*)/i", "$2", $critere_style['style']); 1279 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1280 $current_style['stroke-dasharray'] = $tmp; 1281 } 1282 1283 $tmp = preg_replace("/(.*)stroke-dashoffset:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 1284 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 1285 $current_style['stroke-dashoffset'] = $tmp; 1286 } 1287 } 1288 1289 // mPDF 5.7.2 1290 if (isset($critere_style['opacity']) && $critere_style['opacity'] != 'inherit') { 1291 $current_style['fill-opacity'] = $critere_style['opacity']; 1292 $current_style['stroke-opacity'] = $critere_style['opacity']; 1293 } 1294 1295 if (isset($critere_style['fill']) && $critere_style['fill'] != 'inherit') { 1296 $current_style['fill'] = $critere_style['fill']; 1297 } 1298 1299 if (isset($critere_style['fill-opacity']) && $critere_style['fill-opacity'] != 'inherit') { 1300 $current_style['fill-opacity'] = $critere_style['fill-opacity']; 1301 } 1302 1303 if (isset($critere_style['fill-rule']) && $critere_style['fill-rule'] != 'inherit') { 1304 $current_style['fill-rule'] = $critere_style['fill-rule']; 1305 } 1306 1307 if (isset($critere_style['stroke']) && $critere_style['stroke'] != 'inherit') { 1308 $current_style['stroke'] = $critere_style['stroke']; 1309 } 1310 1311 if (isset($critere_style['stroke-linecap']) && $critere_style['stroke-linecap'] != 'inherit') { 1312 $current_style['stroke-linecap'] = $critere_style['stroke-linecap']; 1313 } 1314 1315 if (isset($critere_style['stroke-linejoin']) && $critere_style['stroke-linejoin'] != 'inherit') { 1316 $current_style['stroke-linejoin'] = $critere_style['stroke-linejoin']; 1317 } 1318 1319 if (isset($critere_style['stroke-miterlimit']) && $critere_style['stroke-miterlimit'] != 'inherit') { 1320 $current_style['stroke-miterlimit'] = $critere_style['stroke-miterlimit']; 1321 } 1322 1323 if (isset($critere_style['stroke-opacity']) && $critere_style['stroke-opacity'] != 'inherit') { 1324 $current_style['stroke-opacity'] = $critere_style['stroke-opacity']; 1325 } 1326 1327 if (isset($critere_style['stroke-width']) && $critere_style['stroke-width'] != 'inherit') { 1328 $current_style['stroke-width'] = $critere_style['stroke-width']; 1329 } 1330 1331 if (isset($critere_style['stroke-dasharray']) && $critere_style['stroke-dasharray'] != 'inherit') { 1332 $current_style['stroke-dasharray'] = $critere_style['stroke-dasharray']; 1333 } 1334 if (isset($critere_style['stroke-dashoffset']) && $critere_style['stroke-dashoffset'] != 'inherit') { 1335 $current_style['stroke-dashoffset'] = $critere_style['stroke-dashoffset']; 1336 } 1337 1338 // Used as indirect setting for currentColor 1339 if (isset($critere_style['color']) && $critere_style['color'] != 'inherit') { 1340 $current_style['color'] = $critere_style['color']; 1341 } 1342 1343 return $current_style; 1344 } 1345 1346 // 1347 // Cette fonction ecrit le style dans le stream svg. 1348 function svgStyle($critere_style, $attribs, $element) 1349 { 1350 $path_style = ''; 1351 $fill_gradient = ''; 1352 $w = ''; 1353 $style = ''; 1354 if (substr_count($critere_style['fill'], 'url') > 0 && $element != 'line') { 1355 // 1356 // couleur degradé 1357 $id_gradient = preg_replace("/url\(#([\w_]*)\)/i", "$1", $critere_style['fill']); 1358 if ($id_gradient != $critere_style['fill']) { 1359 if (isset($this->svg_gradient[$id_gradient])) { 1360 $fill_gradient = $this->svgGradient($this->svg_gradient[$id_gradient], $attribs, $element); 1361 if ($fill_gradient) { 1362 $path_style = "q "; 1363 $w = "W"; 1364 $style .= 'N'; 1365 } 1366 } 1367 } 1368 } // Used as indirect setting for currentColor 1369 elseif (strtolower($critere_style['fill']) == 'currentcolor' && $element != 'line') { 1370 $col = $this->colorConverter->convert($critere_style['color'], $this->mpdf->PDFAXwarnings); 1371 if ($col) { 1372 if ($col[0] == 5 && is_numeric($col[4])) { 1373 $critere_style['fill-opacity'] = ord($col[4] / 100); 1374 } // RGBa 1375 if ($col[0] == 6 && is_numeric($col[5])) { 1376 $critere_style['fill-opacity'] = ord($col[5] / 100); 1377 } // CMYKa 1378 $path_style .= $this->mpdf->SetFColor($col, true) . ' '; 1379 $style .= 'F'; 1380 } 1381 } elseif ($critere_style['fill'] != 'none' && $element != 'line') { 1382 $col = $this->colorConverter->convert($critere_style['fill'], $this->mpdf->PDFAXwarnings); 1383 if ($col) { 1384 if ($col[0] == 5 && is_numeric($col[4])) { 1385 $critere_style['fill-opacity'] = ord($col[4] / 100); 1386 } // RGBa 1387 if ($col[0] == 6 && is_numeric($col[5])) { 1388 $critere_style['fill-opacity'] = ord($col[5] / 100); 1389 } // CMYKa 1390 $path_style .= $this->mpdf->SetFColor($col, true) . ' '; 1391 $style .= 'F'; 1392 } 1393 } 1394 if (substr_count($critere_style['stroke'], 'url') > 0) { 1395 /* 1396 // Cannot put a gradient on a "stroke" in PDF? 1397 $id_gradient = preg_replace("/url\(#([\w_]*)\)/i","$1",$critere_style['stroke']); 1398 if ($id_gradient != $critere_style['stroke']) { 1399 if (isset($this->svg_gradient[$id_gradient])) { 1400 $fill_gradient = $this->svgGradient($this->svg_gradient[$id_gradient], $attribs, $element); 1401 if ($fill_gradient) { 1402 $path_style = "q "; 1403 $w = "W"; 1404 $style .= 'D'; 1405 } 1406 } 1407 } 1408 */ 1409 } // Used as indirect setting for currentColor 1410 elseif (strtolower($critere_style['stroke']) == 'currentcolor') { 1411 $col = $this->colorConverter->convert($critere_style['color'], $this->mpdf->PDFAXwarnings); 1412 if ($col) { 1413 if ($col[0] == 5 && is_numeric($col[4])) { 1414 $critere_style['stroke-opacity'] = ord($col[4] / 100); 1415 } // RGBa 1416 if ($col[0] == 6 && is_numeric($col[5])) { 1417 $critere_style['stroke-opacity'] = ord($col[5] / 100); 1418 } // CMYKa 1419 $path_style .= $this->mpdf->SetDColor($col, true) . ' '; 1420 $style .= 'D'; 1421 $lw = $this->ConvertSVGSizePixels($critere_style['stroke-width']); 1422 $path_style .= sprintf('%.3F w ', $lw * $this->kp); 1423 } 1424 } elseif ($critere_style['stroke'] != 'none') { 1425 $col = $this->colorConverter->convert($critere_style['stroke'], $this->mpdf->PDFAXwarnings); 1426 if ($col) { 1427 // mPDF 5.0.051 1428 // mPDF 5.3.74 1429 if ($col[0] == 5 && is_numeric($col[4])) { 1430 $critere_style['stroke-opacity'] = ord($col[4] / 100); 1431 } // RGBa 1432 if ($col[0] == 6 && is_numeric($col[5])) { 1433 $critere_style['stroke-opacity'] = ord($col[5] / 100); 1434 } // CMYKa 1435 $path_style .= $this->mpdf->SetDColor($col, true) . ' '; 1436 $style .= 'D'; 1437 $lw = $this->ConvertSVGSizePixels($critere_style['stroke-width']); 1438 $path_style .= sprintf('%.3F w ', $lw * $this->kp); 1439 } 1440 } 1441 1442 1443 if ($critere_style['stroke'] != 'none') { 1444 if ($critere_style['stroke-linejoin'] == 'miter') { 1445 $path_style .= ' 0 j '; 1446 } elseif ($critere_style['stroke-linejoin'] == 'round') { 1447 $path_style .= ' 1 j '; 1448 } elseif ($critere_style['stroke-linejoin'] == 'bevel') { 1449 $path_style .= ' 2 j '; 1450 } 1451 1452 if ($critere_style['stroke-linecap'] == 'butt') { 1453 $path_style .= ' 0 J '; 1454 } elseif ($critere_style['stroke-linecap'] == 'round') { 1455 $path_style .= ' 1 J '; 1456 } elseif ($critere_style['stroke-linecap'] == 'square') { 1457 $path_style .= ' 2 J '; 1458 } 1459 1460 if (isset($critere_style['stroke-miterlimit'])) { 1461 if ($critere_style['stroke-miterlimit'] == 'none') { 1462 } elseif (preg_match('/^[\d.]+$/', $critere_style['stroke-miterlimit'])) { 1463 $path_style .= sprintf('%.2F M ', $critere_style['stroke-miterlimit']); 1464 } 1465 } 1466 1467 if (isset($critere_style['stroke-dasharray'])) { 1468 1469 $off = 0; 1470 $d = preg_split('/(,\s?|\s)/', $critere_style['stroke-dasharray']); 1471 1472 if (count($d) == 1 && $d[0] == 0) { 1473 $path_style .= '[] 0 d '; 1474 } else { 1475 1476 if (count($d) % 2 == 1) { 1477 $d = array_merge($d, $d); 1478 } // 5, 3, 1 => 5,3,1,5,3,1 OR 3 => 3,3 1479 1480 $arr = ''; 1481 1482 for ($i = 0; $i < count($d); $i += 2) { 1483 1484 if ($d[$i] === 'none') { 1485 continue; 1486 } 1487 1488 $arr .= sprintf('%.3F %.3F ', $d[$i] * $this->kp, $d[$i + 1] * $this->kp); 1489 } 1490 1491 if (isset($critere_style['stroke-dashoffset'])) { 1492 $off = $critere_style['stroke-dashoffset'] + 0; 1493 } 1494 1495 $path_style .= sprintf('[%s] %.3F d ', $arr, $off * $this->kp); 1496 } 1497 } 1498 } 1499 1500 if ($critere_style['fill-rule'] == 'evenodd') { 1501 $fr = '*'; 1502 } else { 1503 $fr = ''; 1504 } 1505 1506 if (isset($critere_style['fill-opacity'])) { 1507 $opacity = 1; 1508 if ($critere_style['fill-opacity'] == 0) { 1509 $opacity = 0; 1510 } elseif ($critere_style['fill-opacity'] > 1) { 1511 $opacity = 1; 1512 } elseif ($critere_style['fill-opacity'] > 0) { 1513 $opacity = $critere_style['fill-opacity']; 1514 } elseif ($critere_style['fill-opacity'] < 0) { 1515 $opacity = 0; 1516 } 1517 $gs = $this->mpdf->AddExtGState(['ca' => $opacity, 'BM' => '/Normal']); 1518 $this->mpdf->extgstates[$gs]['fo'] = true; 1519 $path_style .= sprintf(' /GS%d gs ', $gs); 1520 } 1521 1522 if (isset($critere_style['stroke-opacity'])) { 1523 $opacity = 1; 1524 if ($critere_style['stroke-opacity'] == 0) { 1525 $opacity = 0; 1526 } elseif ($critere_style['stroke-opacity'] > 1) { 1527 $opacity = 1; 1528 } elseif ($critere_style['stroke-opacity'] > 0) { 1529 $opacity = $critere_style['stroke-opacity']; 1530 } elseif ($critere_style['stroke-opacity'] < 0) { 1531 $opacity = 0; 1532 } 1533 $gs = $this->mpdf->AddExtGState(['CA' => $opacity, 'BM' => '/Normal']); 1534 $this->mpdf->extgstates[$gs]['fo'] = true; 1535 $path_style .= sprintf(' /GS%d gs ', $gs); 1536 } 1537 1538 switch ($style) { 1539 case 'F': 1540 $op = 'f'; 1541 break; 1542 case 'FD': 1543 $op = 'B'; 1544 break; 1545 case 'ND': 1546 $op = 'S'; 1547 break; 1548 case 'D': 1549 $op = 'S'; 1550 break; 1551 default: 1552 $op = 'n'; 1553 } 1554 1555 $prestyle = $path_style . ' '; 1556 $poststyle = $w . ' ' . $op . $fr . ' ' . $fill_gradient . "\n"; 1557 return [$prestyle, $poststyle]; 1558 } 1559 1560 // fonction retracant les <path /> 1561 function svgPath($command, $arguments) 1562 { 1563 $path_cmd = ''; 1564 $newsubpath = false; 1565 // mPDF 5.0.039 1566 $minl = $this->pathBBox[0]; 1567 $mint = $this->pathBBox[1]; 1568 $maxr = $this->pathBBox[2] + $this->pathBBox[0]; 1569 $maxb = $this->pathBBox[3] + $this->pathBBox[1]; 1570 1571 $start = [$this->xbase, -$this->ybase]; 1572 1573 // taken from https://github.com/PhenX/php-svg-lib/blob/master/src/Svg/Tag/Path.php#L47 1574 // Handle args like: a5.38022,5.38022,0,0,1-2.4207.72246,4.50524,4.50524,0,0,1-3.12681-1.33942,9.67442,9.67442,0,0,1-2.38273-3.016,1.87506,1.87506,0,0,1,.34979-2.43562 1575 preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $arguments, $a, PREG_PATTERN_ORDER); 1576 $a = $a[0]; 1577 1578 // if the command is a capital letter, the coords go absolute, otherwise relative 1579 if (strtolower($command) == $command) { 1580 $relative = true; 1581 } else { 1582 $relative = false; 1583 } 1584 1585 1586 $argumentCount = count($a); 1587 1588 // each command may have different needs for arguments [1 to 8] 1589 1590 switch (strtolower($command)) { 1591 1592 case 'm': // move 1593 1594 for ($i = 0; $i < $argumentCount; $i += 2) { 1595 1596 $x = $a[$i]; 1597 $y = $a[$i + 1]; 1598 1599 if ($relative) { 1600 1601 $pdfx = ($this->xbase + $x); 1602 $pdfy = ($this->ybase - $y); 1603 $this->xbase += $x; 1604 $this->ybase += -$y; 1605 1606 } else { 1607 1608 $pdfx = $x; 1609 $pdfy = -$y; 1610 $this->xbase = $x; 1611 $this->ybase = -$y; 1612 } 1613 1614 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1615 1616 $minl = min($minl, $pdf_pt['x']); 1617 $maxr = max($maxr, $pdf_pt['x']); 1618 $mint = min($mint, -$pdf_pt['y']); 1619 $maxb = max($maxb, -$pdf_pt['y']); 1620 1621 if ($i == 0) { 1622 $path_cmd .= sprintf('%.3F %.3F m ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1623 } else { 1624 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1625 } 1626 1627 // mPDF 4.4.003 Save start points of subpath 1628 if ($this->subPathInit) { 1629 $this->spxstart = $this->xbase; 1630 $this->spystart = $this->ybase; 1631 $this->subPathInit = false; 1632 } 1633 } 1634 1635 break; 1636 1637 case 'l': // a simple line 1638 1639 for ($i = 0; $i < $argumentCount; $i+=2) { 1640 1641 $x = ($a[$i]); 1642 $y = ($a[$i + 1]); 1643 1644 if ($relative) { 1645 1646 $pdfx = ($this->xbase + $x); 1647 $pdfy = ($this->ybase - $y); 1648 $this->xbase += $x; 1649 $this->ybase += -$y; 1650 1651 } else { 1652 1653 $pdfx = $x; 1654 $pdfy = -$y; 1655 $this->xbase = $x; 1656 $this->ybase = -$y; 1657 1658 } 1659 1660 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1661 1662 $minl = min($minl, $pdf_pt['x']); 1663 $maxr = max($maxr, $pdf_pt['x']); 1664 $mint = min($mint, -$pdf_pt['y']); 1665 $maxb = max($maxb, -$pdf_pt['y']); 1666 1667 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1668 } 1669 1670 break; 1671 1672 case 'h': // a very simple horizontal line 1673 1674 for ($i = 0; $i < $argumentCount; $i++) { 1675 1676 $x = ($a[$i]); 1677 1678 if ($relative) { 1679 1680 $y = 0; 1681 $pdfx = ($this->xbase + $x); 1682 $pdfy = ($this->ybase - $y); 1683 $this->xbase += $x; 1684 $this->ybase += -$y; 1685 1686 } else { 1687 1688 $y = -$this->ybase; 1689 $pdfx = $x; 1690 $pdfy = -$y; 1691 $this->xbase = $x; 1692 $this->ybase = -$y; 1693 1694 } 1695 1696 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1697 $minl = min($minl, $pdf_pt['x']); 1698 $maxr = max($maxr, $pdf_pt['x']); 1699 $mint = min($mint, -$pdf_pt['y']); 1700 $maxb = max($maxb, -$pdf_pt['y']); 1701 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1702 } 1703 1704 break; 1705 1706 case 'v': // the simplest line, vertical 1707 1708 for ($i = 0; $i < $argumentCount; $i++) { 1709 1710 $y = ($a[$i]); 1711 1712 if ($relative) { 1713 1714 $x = 0; 1715 $pdfx = ($this->xbase + $x); 1716 $pdfy = ($this->ybase - $y); 1717 $this->xbase += $x; 1718 $this->ybase += -$y; 1719 1720 } else { 1721 1722 $x = $this->xbase; 1723 $pdfx = $x; 1724 $pdfy = -$y; 1725 $this->xbase = $x; 1726 $this->ybase = -$y; 1727 1728 } 1729 1730 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1731 $minl = min($minl, $pdf_pt['x']); 1732 $maxr = max($maxr, $pdf_pt['x']); 1733 $mint = min($mint, -$pdf_pt['y']); 1734 $maxb = max($maxb, -$pdf_pt['y']); 1735 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1736 1737 } 1738 1739 break; 1740 1741 case 's': // bezier with first vertex equal first control 1742 1743 // mPDF 4.4.003 1744 1745 if (!($this->lastcommand == 'C' || $this->lastcommand == 'c' || $this->lastcommand == 'S' || $this->lastcommand == 's')) { 1746 $this->lastcontrolpoints = [0, 0]; 1747 } 1748 1749 for ($i = 0; $i < $argumentCount; $i += 4) { 1750 1751 $x1 = $this->lastcontrolpoints[0]; 1752 $y1 = $this->lastcontrolpoints[1]; 1753 1754 $x2 = ($a[$i]); 1755 $y2 = ($a[$i + 1]); 1756 1757 $x = ($a[$i + 2]); 1758 $y = ($a[$i + 3]); 1759 1760 if ($relative) { 1761 1762 $pdfx1 = ($this->xbase + $x1); 1763 $pdfy1 = ($this->ybase - $y1); 1764 $pdfx2 = ($this->xbase + $x2); 1765 $pdfy2 = ($this->ybase - $y2); 1766 $pdfx = ($this->xbase + $x); 1767 $pdfy = ($this->ybase - $y); 1768 $this->xbase += $x; 1769 $this->ybase += -$y; 1770 1771 } else { 1772 1773 $pdfx1 = $this->xbase + $x1; 1774 $pdfy1 = $this->ybase - $y1; 1775 $pdfx2 = $x2; 1776 $pdfy2 = -$y2; 1777 $pdfx = $x; 1778 $pdfy = -$y; 1779 $this->xbase = $x; 1780 $this->ybase = -$y; 1781 1782 } 1783 1784 $this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative 1785 1786 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1787 1788 $curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy]; 1789 $bx = $this->computeBezierBoundingBox($start, $curves); 1790 $minl = min($minl, $bx[0]); 1791 $maxr = max($maxr, $bx[2]); 1792 $mint = min($mint, $bx[1]); 1793 $maxb = max($maxb, $bx[3]); 1794 1795 if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) { 1796 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1797 } else { 1798 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp); 1799 } 1800 } 1801 1802 break; 1803 1804 case 'c': // bezier with second vertex equal second control 1805 1806 for ($i = 0; $i < $argumentCount; $i += 6) { 1807 1808 $x1 = ($a[$i]); 1809 $y1 = ($a[$i + 1]); 1810 $x2 = ($a[$i + 2]); 1811 $y2 = ($a[$i + 3]); 1812 $x = ($a[$i + 4]); 1813 $y = ($a[$i + 5]); 1814 1815 if ($relative) { 1816 1817 $pdfx1 = ($this->xbase + $x1); 1818 $pdfy1 = ($this->ybase - $y1); 1819 $pdfx2 = ($this->xbase + $x2); 1820 $pdfy2 = ($this->ybase - $y2); 1821 $pdfx = ($this->xbase + $x); 1822 $pdfy = ($this->ybase - $y); 1823 $this->xbase += $x; 1824 $this->ybase += -$y; 1825 1826 } else { 1827 1828 $pdfx1 = $x1; 1829 $pdfy1 = -$y1; 1830 $pdfx2 = $x2; 1831 $pdfy2 = -$y2; 1832 $pdfx = $x; 1833 $pdfy = -$y; 1834 $this->xbase = $x; 1835 $this->ybase = -$y; 1836 1837 } 1838 1839 $this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative 1840 // $pdf_pt2 = $this->svg_overflow($pdfx2,$pdfy2); 1841 // $pdf_pt1 = $this->svg_overflow($pdfx1,$pdfy1); 1842 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1843 1844 $curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy]; 1845 $bx = $this->computeBezierBoundingBox($start, $curves); 1846 $minl = min($minl, $bx[0]); 1847 $maxr = max($maxr, $bx[2]); 1848 $mint = min($mint, $bx[1]); 1849 $maxb = max($maxb, $bx[3]); 1850 1851 if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) { 1852 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1853 } else { 1854 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp); 1855 } 1856 } 1857 1858 break; 1859 1860 case 'q': // bezier quadratic avec point de control 1861 1862 for ($i = 0; $i < $argumentCount; $i += 4) { 1863 1864 $x1 = ($a[$i]); 1865 $y1 = ($a[$i + 1]); 1866 $x = ($a[$i + 2]); 1867 $y = ($a[$i + 3]); 1868 1869 if ($relative) { 1870 1871 $pdfx = ($this->xbase + $x); 1872 $pdfy = ($this->ybase - $y); 1873 1874 $pdfx1 = ($this->xbase + ($x1 * 2 / 3)); 1875 $pdfy1 = ($this->ybase - ($y1 * 2 / 3)); 1876 // mPDF 4.4.003 1877 $pdfx2 = $pdfx1 + 1 / 3 * ($x); 1878 $pdfy2 = $pdfy1 + 1 / 3 * (-$y); 1879 1880 $this->xbase += $x; 1881 $this->ybase += -$y; 1882 1883 } else { 1884 1885 $pdfx = $x; 1886 $pdfy = -$y; 1887 1888 $pdfx1 = ($this->xbase + (($x1 - $this->xbase) * 2 / 3)); 1889 $pdfy1 = ($this->ybase - (($y1 + $this->ybase) * 2 / 3)); 1890 1891 $pdfx2 = ($x + (($x1 - $x) * 2 / 3)); 1892 $pdfy2 = (-$y - (($y1 - $y) * 2 / 3)); 1893 1894 // mPDF 4.4.003 1895 $pdfx2 = $pdfx1 + 1 / 3 * ($x - $this->xbase); 1896 $pdfy2 = $pdfy1 + 1 / 3 * (-$y - $this->ybase); 1897 1898 $this->xbase = $x; 1899 $this->ybase = -$y; 1900 1901 } 1902 1903 $this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative 1904 1905 $pdf_pt = $this->svg_overflow($pdfx, $pdfy); 1906 1907 $curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy]; 1908 $bx = $this->computeBezierBoundingBox($start, $curves); 1909 $minl = min($minl, $bx[0]); 1910 $maxr = max($maxr, $bx[2]); 1911 $mint = min($mint, $bx[1]); 1912 $maxb = max($maxb, $bx[3]); 1913 1914 if (($pdf_pt['x'] != $pdfx) || ($pdf_pt['y'] != $pdfy)) { 1915 $path_cmd .= sprintf('%.3F %.3F l ', $pdf_pt['x'] * $this->kp, $pdf_pt['y'] * $this->kp); 1916 } else { 1917 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp); 1918 } 1919 } 1920 1921 break; 1922 1923 case 't': // bezier quadratic avec point de control simetrique a lancien point de control 1924 1925 if (!($this->lastcommand == 'Q' || $this->lastcommand == 'q' || $this->lastcommand == 'T' || $this->lastcommand == 't')) { 1926 $this->lastcontrolpoints = [0, 0]; 1927 } 1928 1929 for ($i = 0; $i < $argumentCount; $i += 2) { 1930 1931 $x = ($a[$i]); 1932 $y = ($a[$i + 1]); 1933 1934 $x1 = $this->lastcontrolpoints[0]; 1935 $y1 = $this->lastcontrolpoints[1]; 1936 1937 if ($relative) { 1938 $pdfx = ($this->xbase + $x); 1939 $pdfy = ($this->ybase - $y); 1940 1941 $pdfx1 = ($this->xbase + ($x1)); 1942 $pdfy1 = ($this->ybase - ($y1)); 1943 // mPDF 4.4.003 1944 $pdfx2 = $pdfx1 + 1 / 3 * ($x); 1945 $pdfy2 = $pdfy1 + 1 / 3 * (-$y); 1946 1947 $this->xbase += $x; 1948 $this->ybase += -$y; 1949 1950 } else { 1951 1952 $pdfx = $x; 1953 $pdfy = -$y; 1954 1955 $pdfx1 = ($this->xbase + ($x1)); 1956 $pdfy1 = ($this->ybase - ($y1)); 1957 1958 // mPDF 4.4.003 1959 $pdfx2 = $pdfx1 + 1 / 3 * ($x - $this->xbase); 1960 $pdfy2 = $pdfy1 + 1 / 3 * (-$y - $this->ybase); 1961 1962 $this->xbase = $x; 1963 $this->ybase = -$y; 1964 } 1965 1966 $this->lastcontrolpoints = [($pdfx - $pdfx2), -($pdfy - $pdfy2)]; // mPDF 4.4.003 always relative 1967 1968 $curves = [$pdfx1, -$pdfy1, $pdfx2, -$pdfy2, $pdfx, -$pdfy]; 1969 $bx = $this->computeBezierBoundingBox($start, $curves); 1970 $minl = min($minl, $bx[0]); 1971 $maxr = max($maxr, $bx[2]); 1972 $mint = min($mint, $bx[1]); 1973 $maxb = max($maxb, $bx[3]); 1974 1975 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $pdfx1 * $this->kp, $pdfy1 * $this->kp, $pdfx2 * $this->kp, $pdfy2 * $this->kp, $pdfx * $this->kp, $pdfy * $this->kp); 1976 } 1977 1978 break; 1979 1980 case 'a': // Elliptical arc 1981 1982 for ($i = 0; $i < $argumentCount; $i += 7) { 1983 1984 $rx = isset($a[$i]) ? $a[$i] : 0; 1985 $ry = isset($a[$i + 1]) ? $a[$i + 1] : 0; 1986 1987 // x-axis-rotation 1988 $angle = isset($a[$i + 2]) ? $a[$i + 2] : 0; 1989 1990 $largeArcFlag = isset($a[$i + 3]) ? $a[$i + 3] : 0; 1991 $sweepFlag = isset($a[$i + 4]) ? $a[$i + 4] : 0; 1992 1993 $x2 = isset($a[$i + 5]) ? $a[$i + 5] : 0; 1994 $y2 = isset($a[$i + 6]) ? $a[$i + 6] : 0; 1995 1996 $x1 = $this->xbase; 1997 $y1 = -$this->ybase; 1998 1999 if ($relative) { 2000 $x2 = $this->xbase + $x2; 2001 $y2 = -$this->ybase + $y2; 2002 $this->xbase += isset($a[$i + 5]) ? $a[$i + 5] : 0; 2003 $this->ybase += isset($a[$i + 6]) ? -$a[$i + 6] : 0; 2004 } else { 2005 $this->xbase = $x2; 2006 $this->ybase = -$y2; 2007 } 2008 2009 list($pcmd, $bounds) = $this->Arcto($x1, $y1, $x2, $y2, $rx, $ry, $angle, $largeArcFlag, $sweepFlag); 2010 2011 $minl = min($minl, $x2, min($bounds[0])); 2012 $maxr = max($maxr, $x2, max($bounds[0])); 2013 $mint = min($mint, $y2, min($bounds[1])); 2014 $maxb = max($maxb, $y2, max($bounds[1])); 2015 2016 $path_cmd .= $pcmd; 2017 2018 } 2019 2020 break; 2021 2022 case 'z': 2023 2024 $path_cmd .= 'h '; 2025 2026 $this->subPathInit = true; 2027 $newsubpath = true; 2028 $this->xbase = $this->spxstart; 2029 $this->ybase = $this->spystart; 2030 2031 break; 2032 } 2033 2034 if (!$newsubpath) { 2035 $this->subPathInit = false; 2036 } 2037 2038 $this->lastcommand = $command; 2039 2040 // mPDF 5.0.039 2041 $this->pathBBox[0] = $minl; 2042 $this->pathBBox[1] = $mint; 2043 $this->pathBBox[2] = $maxr - $this->pathBBox[0]; 2044 $this->pathBBox[3] = $maxb - $this->pathBBox[1]; 2045 2046 return $path_cmd; 2047 } 2048 2049 function Arcto($x1, $y1, $x2, $y2, $rx, $ry, $angle, $largeArcFlag, $sweepFlag) 2050 { 2051 2052 $bounds = [0 => [$x1, $x2], 1 => [$y1, $y2]]; 2053 2054 // 1. Treat out-of-range parameters as described in 2055 // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 2056 // If the endpoints (x1, y1) and (x2, y2) are identical, then this 2057 // is equivalent to omitting the elliptical arc segment entirely 2058 if ($x1 == $x2 && $y1 == $y2) { 2059 return ['', $bounds]; // mPD 5.0.040 2060 } 2061 2062 // If rX = 0 or rY = 0 then this arc is treated as a straight line 2063 // segment (a "lineto") joining the endpoints. 2064 if ($rx == 0.0 || $ry == 0.0) { 2065 // return array(Lineto(x2, y2), $bounds); 2066 } 2067 2068 // If rX or rY have negative signs, these are dropped; the absolute 2069 // value is used instead. 2070 if ($rx < 0.0) { 2071 $rx = -$rx; 2072 } 2073 2074 if ($ry < 0.0) { 2075 $ry = -$ry; 2076 } 2077 2078 // 2. convert to center parameterization as shown in 2079 // http://www.w3.org/TR/SVG/implnote.html 2080 $sinPhi = sin(deg2rad($angle)); 2081 $cosPhi = cos(deg2rad($angle)); 2082 2083 $x1dash = $cosPhi * ($x1 - $x2) / 2.0 + $sinPhi * ($y1 - $y2) / 2.0; 2084 $y1dash = -$sinPhi * ($x1 - $x2) / 2.0 + $cosPhi * ($y1 - $y2) / 2.0; 2085 2086 $numerator = $rx * $rx * $ry * $ry - $rx * $rx * $y1dash * $y1dash - $ry * $ry * $x1dash * $x1dash; 2087 2088 if ($numerator < 0.0) { 2089 2090 // If rX , rY and are such that there is no solution (basically, 2091 // the ellipse is not big enough to reach from (x1, y1) to (x2, 2092 // y2)) then the ellipse is scaled up uniformly until there is 2093 // exactly one solution (until the ellipse is just big enough). 2094 // -> find factor s, such that numerator' with rx'=s*rx and 2095 // ry'=s*ry becomes 0 : 2096 $s = sqrt(1.0 - $numerator / ($rx * $rx * $ry * $ry)); 2097 2098 $rx *= $s; 2099 $ry *= $s; 2100 $root = 0.0; 2101 2102 } else { 2103 $root = ($largeArcFlag == $sweepFlag ? -1.0 : 1.0) * sqrt($numerator / ($rx * $rx * $y1dash * $y1dash + $ry * $ry * $x1dash * $x1dash)); 2104 } 2105 2106 $cxdash = $root * $rx * $y1dash / $ry; 2107 $cydash = -$root * $ry * $x1dash / $rx; 2108 2109 $cx = $cosPhi * $cxdash - $sinPhi * $cydash + ($x1 + $x2) / 2.0; 2110 $cy = $sinPhi * $cxdash + $cosPhi * $cydash + ($y1 + $y2) / 2.0; 2111 2112 2113 $theta1 = $this->CalcVectorAngle(1.0, 0.0, ($x1dash - $cxdash) / $rx, ($y1dash - $cydash) / $ry); 2114 $dtheta = $this->CalcVectorAngle(($x1dash - $cxdash) / $rx, ($y1dash - $cydash) / $ry, (-$x1dash - $cxdash) / $rx, (-$y1dash - $cydash) / $ry); 2115 2116 if (!$sweepFlag && $dtheta > 0) { 2117 $dtheta -= 2.0 * M_PI; 2118 } elseif ($sweepFlag && $dtheta < 0) { 2119 $dtheta += 2.0 * M_PI; 2120 } 2121 2122 // 3. convert into cubic bezier segments <= 90deg 2123 $segments = ceil(abs($dtheta / (M_PI / 2.0))); 2124 $delta = $dtheta / $segments; 2125 $t = 8.0 / 3.0 * sin($delta / 4.0) * sin($delta / 4.0) / sin($delta / 2.0); 2126 $coords = []; 2127 2128 for ($i = 0; $i < $segments; $i++) { 2129 2130 $cosTheta1 = cos($theta1); 2131 $sinTheta1 = sin($theta1); 2132 2133 $theta2 = $theta1 + $delta; 2134 2135 $cosTheta2 = cos($theta2); 2136 $sinTheta2 = sin($theta2); 2137 2138 // a) calculate endpoint of the segment: 2139 $xe = $cosPhi * $rx * $cosTheta2 - $sinPhi * $ry * $sinTheta2 + $cx; 2140 $ye = $sinPhi * $rx * $cosTheta2 + $cosPhi * $ry * $sinTheta2 + $cy; 2141 2142 // b) calculate gradients at start/end points of segment: 2143 $dx1 = $t * ( - $cosPhi * $rx * $sinTheta1 - $sinPhi * $ry * $cosTheta1); 2144 $dy1 = $t * ( - $sinPhi * $rx * $sinTheta1 + $cosPhi * $ry * $cosTheta1); 2145 2146 $dxe = $t * ( $cosPhi * $rx * $sinTheta2 + $sinPhi * $ry * $cosTheta2); 2147 $dye = $t * ( $sinPhi * $rx * $sinTheta2 - $cosPhi * $ry * $cosTheta2); 2148 2149 // c) draw the cubic bezier: 2150 $coords[$i] = [($x1 + $dx1), ($y1 + $dy1), ($xe + $dxe), ($ye + $dye), $xe, $ye]; 2151 2152 // do next segment 2153 $theta1 = $theta2; 2154 $x1 = $xe; 2155 $y1 = $ye; 2156 } 2157 2158 $path = ' '; 2159 2160 foreach ($coords as $c) { 2161 2162 $cpx1 = $c[0]; 2163 $cpy1 = $c[1]; 2164 $cpx2 = $c[2]; 2165 $cpy2 = $c[3]; 2166 $x2 = $c[4]; 2167 $y2 = $c[5]; 2168 $path .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $cpx1 * $this->kp, -$cpy1 * $this->kp, $cpx2 * $this->kp, -$cpy2 * $this->kp, $x2 * $this->kp, -$y2 * $this->kp) . "\n"; 2169 2170 // mPDF 5.0.040 2171 $bounds[0][] = $c[4]; 2172 $bounds[1][] = $c[5]; 2173 } 2174 2175 return [$path, $bounds]; // mPDF 5.0.040 2176 } 2177 2178 function CalcVectorAngle($ux, $uy, $vx, $vy) 2179 { 2180 $ta = atan2($uy, $ux); 2181 $tb = atan2($vy, $vx); 2182 2183 if ($tb >= $ta) { 2184 return ($tb - $ta); 2185 } 2186 2187 return (6.28318530718 - ($ta - $tb)); 2188 } 2189 2190 function ConvertSVGSizePixels($size = 5, $maxsize = 'x') 2191 { 2192 // maxsize in pixels (user units) or 'y' or 'x' 2193 // e.g. $w = $this->ConvertSVGSizePixels($arguments['w'],$this->svg_info['w']*(25.4/$this->mpdf->dpi)); 2194 // usefontsize - setfalse for e.g. margins - will ignore fontsize for % values 2195 // Depends of maxsize value to make % work properly. Usually maxsize == pagewidth 2196 // For text $maxsize = Fontsize 2197 // Setting e.g. margin % will use maxsize (pagewidth) and em will use fontsize 2198 2199 if ($maxsize == 'y') { 2200 $maxsize = $this->svg_info['h']; 2201 } elseif ($maxsize == 'x') { 2202 $maxsize = $this->svg_info['w']; 2203 } 2204 2205 $maxsize *= (25.4 / $this->mpdf->dpi); // convert pixels to mm 2206 $fontsize = $this->mpdf->FontSize / $this->kf; 2207 2208 // Return as pixels 2209 $size = $this->sizeConverter->convert($size, $maxsize, $fontsize, false) * 1 / (25.4 / $this->mpdf->dpi); 2210 2211 return $size; 2212 } 2213 2214 function ConvertSVGSizePts($size = 5) 2215 { 2216 // usefontsize - setfalse for e.g. margins - will ignore fontsize for % values 2217 // Depends of maxsize value to make % work properly. Usually maxsize == pagewidth 2218 // For text $maxsize = Fontsize 2219 // Setting e.g. margin % will use maxsize (pagewidth) and em will use fontsize 2220 $maxsize = $this->mpdf->FontSize; 2221 2222 // Return as pts 2223 $size = $this->sizeConverter->convert($size, $maxsize, false, true) * 72 / 25.4; 2224 2225 return $size; 2226 } 2227 2228 function svgRect($arguments) 2229 { 2230 if ($arguments['h'] == 0 || $arguments['w'] == 0) { 2231 return ''; 2232 } 2233 2234 $x = $this->ConvertSVGSizePixels($arguments['x'], 'x'); // mPDF 4.4.003 2235 $y = $this->ConvertSVGSizePixels($arguments['y'], 'y'); // mPDF 4.4.003 2236 $h = $this->ConvertSVGSizePixels($arguments['h'], 'y'); // mPDF 4.4.003 2237 $w = $this->ConvertSVGSizePixels($arguments['w'], 'x'); // mPDF 4.4.003 2238 $rx = $this->ConvertSVGSizePixels($arguments['rx'], 'x'); // mPDF 4.4.003 2239 $ry = $this->ConvertSVGSizePixels($arguments['ry'], 'y'); // mPDF 4.4.003 2240 2241 // mPDF 4.4.003 2242 if ($rx > $w / 2) { 2243 $rx = $w / 2; 2244 } 2245 2246 // mPDF 4.4.003 2247 if ($ry > $h / 2) { 2248 $ry = $h / 2; 2249 } 2250 2251 if ($rx > 0 and $ry == 0) { 2252 $ry = $rx; 2253 } 2254 2255 if ($ry > 0 and $rx == 0) { 2256 $rx = $ry; 2257 } 2258 2259 if ($rx == 0 and $ry == 0) { 2260 // trace un rectangle sans angle arrondit 2261 $path_cmd = sprintf('%.3F %.3F m ', ($x * $this->kp), -($y * $this->kp)); 2262 $path_cmd .= sprintf('%.3F %.3F l ', (($x + $w) * $this->kp), -($y * $this->kp)); 2263 $path_cmd .= sprintf('%.3F %.3F l ', (($x + $w) * $this->kp), -(($y + $h) * $this->kp)); 2264 $path_cmd .= sprintf('%.3F %.3F l ', ($x) * $this->kp, -(($y + $h) * $this->kp)); 2265 $path_cmd .= sprintf('%.3F %.3F l h ', ($x * $this->kp), -($y * $this->kp)); 2266 } else { 2267 // trace un rectangle avec les arrondit 2268 // les points de controle du bezier sont deduis grace a la constante kappa 2269 $kappa = 4 * (sqrt(2) - 1) / 3; 2270 2271 $kx = $kappa * $rx; 2272 $ky = $kappa * $ry; 2273 2274 $path_cmd = sprintf('%.3F %.3F m ', ($x + $rx) * $this->kp, -$y * $this->kp); 2275 $path_cmd .= sprintf('%.3F %.3F l ', ($x + ($w - $rx)) * $this->kp, -$y * $this->kp); 2276 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + ($w - $rx + $kx)) * $this->kp, -$y * $this->kp, ($x + $w) * $this->kp, (-$y + (-$ry + $ky)) * $this->kp, ($x + $w) * $this->kp, (-$y + (-$ry)) * $this->kp); 2277 $path_cmd .= sprintf('%.3F %.3F l ', ($x + $w) * $this->kp, (-$y + (-$h + $ry)) * $this->kp); 2278 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + $w) * $this->kp, (-$y + (-$h - $ky + $ry)) * $this->kp, ($x + ($w - $rx + $kx)) * $this->kp, (-$y + (-$h)) * $this->kp, ($x + ($w - $rx)) * $this->kp, (-$y + (-$h)) * $this->kp); 2279 2280 $path_cmd .= sprintf('%.3F %.3F l ', ($x + $rx) * $this->kp, (-$y + (-$h)) * $this->kp); 2281 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x + ($rx - $kx)) * $this->kp, (-$y + (-$h)) * $this->kp, $x * $this->kp, (-$y + (-$h - $ky + $ry)) * $this->kp, $x * $this->kp, (-$y + (-$h + $ry)) * $this->kp); 2282 $path_cmd .= sprintf('%.3F %.3F l ', $x * $this->kp, (-$y + (-$ry)) * $this->kp); 2283 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c h ', $x * $this->kp, (-$y + (-$ry + $ky)) * $this->kp, ($x + ($rx - $kx)) * $this->kp, -$y * $this->kp, ($x + $rx) * $this->kp, -$y * $this->kp); 2284 } 2285 return $path_cmd; 2286 } 2287 2288 /** 2289 * fonction retracant les <ellipse /> et <circle /> 2290 * le cercle est tracé grave a 4 bezier cubic, les poitn de controles 2291 * sont deduis grace a la constante kappa * rayon 2292 */ 2293 function svgEllipse($arguments) 2294 { 2295 if ($arguments['rx'] == 0 || $arguments['ry'] == 0) { 2296 return ''; 2297 } 2298 2299 $kappa = 4 * (sqrt(2) - 1) / 3; 2300 2301 $cx = $this->ConvertSVGSizePixels($arguments['cx'], 'x'); 2302 $cy = $this->ConvertSVGSizePixels($arguments['cy'], 'y'); 2303 $rx = $this->ConvertSVGSizePixels($arguments['rx'], 'x'); 2304 $ry = $this->ConvertSVGSizePixels($arguments['ry'], 'y'); 2305 2306 $x1 = $cx; 2307 $y1 = -$cy + $ry; 2308 2309 $x2 = $cx + $rx; 2310 $y2 = -$cy; 2311 2312 $x3 = $cx; 2313 $y3 = -$cy - $ry; 2314 2315 $x4 = $cx - $rx; 2316 $y4 = -$cy; 2317 2318 $path_cmd = sprintf('%.3F %.3F m ', $x1 * $this->kp, $y1 * $this->kp); 2319 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x1 + ($rx * $kappa)) * $this->kp, $y1 * $this->kp, $x2 * $this->kp, ($y2 + ($ry * $kappa)) * $this->kp, $x2 * $this->kp, $y2 * $this->kp); 2320 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $x2 * $this->kp, ($y2 - ($ry * $kappa)) * $this->kp, ($x3 + ($rx * $kappa)) * $this->kp, $y3 * $this->kp, $x3 * $this->kp, $y3 * $this->kp); 2321 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($x3 - ($rx * $kappa)) * $this->kp, $y3 * $this->kp, $x4 * $this->kp, ($y4 - ($ry * $kappa)) * $this->kp, $x4 * $this->kp, $y4 * $this->kp); 2322 $path_cmd .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', $x4 * $this->kp, ($y4 + ($ry * $kappa)) * $this->kp, ($x1 - ($rx * $kappa)) * $this->kp, $y1 * $this->kp, $x1 * $this->kp, $y1 * $this->kp); 2323 $path_cmd .= 'h '; 2324 2325 return $path_cmd; 2326 } 2327 2328 function svgPolyline($arguments, $ispolyline = true) 2329 { 2330 if ($ispolyline) { 2331 $xbase = $arguments[0]; 2332 $ybase = - $arguments[1]; 2333 } else { 2334 if ($arguments[0] == $arguments[2] && $arguments[1] == $arguments[3]) { 2335 return ''; 2336 } // Zero length line 2337 $xbase = $this->ConvertSVGSizePixels($arguments[0], 'x'); 2338 $ybase = - $this->ConvertSVGSizePixels($arguments[1], 'y'); 2339 } 2340 2341 $path_cmd = sprintf('%.3F %.3F m ', $xbase * $this->kp, $ybase * $this->kp); 2342 2343 for ($i = 2; $i < count($arguments); $i += 2) { 2344 if ($ispolyline) { 2345 $tmp_x = $arguments[$i]; 2346 $tmp_y = - $arguments[($i + 1)]; 2347 } else { 2348 $tmp_x = $this->ConvertSVGSizePixels($arguments[$i], 'x'); 2349 $tmp_y = - $this->ConvertSVGSizePixels($arguments[($i + 1)], 'y'); 2350 } 2351 $path_cmd .= sprintf('%.3F %.3F l ', $tmp_x * $this->kp, $tmp_y * $this->kp); 2352 } 2353 2354 // $path_cmd .= 'h '; // ?? In error - don't close subpath here 2355 return $path_cmd; 2356 } 2357 2358 function svgPolygon($arguments) 2359 { 2360 $xbase = $arguments[0]; 2361 $ybase = - $arguments[1]; 2362 $path_cmd = sprintf('%.3F %.3F m ', $xbase * $this->kp, $ybase * $this->kp); 2363 2364 for ($i = 2; $i < count($arguments); $i += 2) { 2365 $tmp_x = $arguments[$i]; 2366 $tmp_y = - $arguments[($i + 1)]; 2367 2368 $path_cmd .= sprintf('%.3F %.3F l ', $tmp_x * $this->kp, $tmp_y * $this->kp); 2369 } 2370 2371 $path_cmd .= sprintf('%.3F %.3F l ', $xbase * $this->kp, $ybase * $this->kp); 2372 $path_cmd .= 'h '; 2373 2374 return $path_cmd; 2375 } 2376 2377 function svgText() 2378 { 2379 $current_style = $this->txt_style[count($this->txt_style) - 1]; // mPDF 5.7.4 2380 $style = ''; 2381 $op = ''; 2382 $render = -1; 2383 2384 if (isset($this->txt_data[2])) { 2385 2386 // mPDF 6 2387 // If using SVG Font 2388 if (isset($this->svg_font[$current_style['font-family']])) { 2389 2390 // select font 2391 $style = 'R'; 2392 $style .= (isset($current_style['font-weight']) && $current_style['font-weight'] == 'bold') ? 'B' : ''; 2393 $style .= (isset($current_style['font-style']) && $current_style['font-style'] == 'italic') ? 'I' : ''; 2394 $style .= (isset($current_style['font-variant']) && $current_style['font-variant'] == 'small-caps') ? 'S' : ''; 2395 2396 $fontsize = $current_style['font-size'] * $this->mpdf->dpi / 72; 2397 2398 if (isset($this->svg_font[$current_style['font-family']][$style])) { 2399 $svg_font = $this->svg_font[$current_style['font-family']][$style]; 2400 } elseif (isset($this->svg_font[$current_style['font-family']]['R'])) { 2401 $svg_font = $this->svg_font[$current_style['font-family']]['R']; 2402 } 2403 2404 if (!isset($svg_font['units-per-em']) || $svg_font['units-per-em'] < 1) { 2405 $svg_font['units-per-em'] = 1000; 2406 } 2407 2408 $units_per_em = $svg_font['units-per-em']; 2409 $scale = $fontsize / $units_per_em; 2410 $stroke_width = $current_style['stroke-width']; 2411 $stroke_width /= $scale; 2412 2413 $opacitystr = ''; 2414 $fopacity = 1; 2415 2416 if (isset($current_style['fill-opacity'])) { 2417 if ($current_style['fill-opacity'] == 0) { 2418 $fopacity = 0; 2419 } elseif ($current_style['fill-opacity'] > 1) { 2420 $fopacity = 1; 2421 } elseif ($current_style['fill-opacity'] > 0) { 2422 $fopacity = $current_style['fill-opacity']; 2423 } elseif ($current_style['fill-opacity'] < 0) { 2424 $fopacity = 0; 2425 } 2426 } 2427 2428 $sopacity = 1; 2429 if (isset($current_style['stroke-opacity'])) { 2430 if ($current_style['stroke-opacity'] == 0) { 2431 $sopacity = 0; 2432 } elseif ($current_style['stroke-opacity'] > 1) { 2433 $sopacity = 1; 2434 } elseif ($current_style['stroke-opacity'] > 0) { 2435 $sopacity = $current_style['stroke-opacity']; 2436 } elseif ($current_style['stroke-opacity'] < 0) { 2437 $sopacity = 0; 2438 } 2439 } 2440 2441 $gs = $this->mpdf->AddExtGState(['ca' => $fopacity, 'CA' => $sopacity, 'BM' => '/Normal']); 2442 $this->mpdf->extgstates[$gs]['fo'] = true; 2443 $opacitystr = sprintf(' /GS%d gs ', $gs); 2444 2445 $fillstr = ''; 2446 if (isset($current_style['fill']) && $current_style['fill'] != 'none') { 2447 $col = $this->colorConverter->convert($current_style['fill'], $this->mpdf->PDFAXwarnings); 2448 $fillstr = $this->mpdf->SetFColor($col, true); 2449 $render = "0"; // Fill (only) 2450 $op = 'f'; 2451 } 2452 2453 $strokestr = ''; 2454 if ($stroke_width > 0 && $current_style['stroke'] != 'none') { 2455 $scol = $this->colorConverter->convert($current_style['stroke'], $this->mpdf->PDFAXwarnings); 2456 if ($scol) { 2457 $strokestr .= $this->mpdf->SetDColor($scol, true) . ' '; 2458 } 2459 $linewidth = $this->ConvertSVGSizePixels($stroke_width); 2460 if ($linewidth > 0) { 2461 $strokestr .= sprintf('%.3F w 0 J 0 j ', $linewidth * $this->kp); 2462 if ($render == -1) { 2463 $render = "1"; 2464 } // stroke only 2465 else { 2466 $render = "2"; 2467 } // fill and stroke 2468 $op .= 'S'; 2469 } 2470 } 2471 2472 if ($render == -1) { 2473 return ''; 2474 } 2475 2476 if ($op == 'fS') { 2477 $op = 'B'; 2478 } 2479 2480 $x = $this->txt_data[0]; // mPDF 5.7.4 2481 $y = $this->txt_data[1]; // mPDF 5.7.4 2482 $txt = $this->txt_data[2]; 2483 2484 $txt = preg_replace('/\f/', '', $txt); 2485 $txt = preg_replace('/\r/', '', $txt); 2486 $txt = preg_replace('/\n/', ' ', $txt); 2487 $txt = preg_replace('/\t/', ' ', $txt); 2488 $txt = preg_replace("/[ ]+/u", ' ', $txt); 2489 2490 if ($this->textjuststarted) { 2491 $txt = ltrim($txt); 2492 } // mPDF 5.7.4 2493 2494 $this->textjuststarted = false; // mPDF 5.7.4 2495 2496 $txt = $this->mpdf->purify_utf8_text($txt); 2497 2498 if ($this->mpdf->text_input_as_HTML) { 2499 $txt = $this->mpdf->all_entities_to_utf8($txt); 2500 } 2501 2502 $nb = mb_strlen($txt, 'UTF-8'); 2503 $i = 0; 2504 $sw = 0; 2505 $subpath_cmd = ''; 2506 2507 while ($i < $nb) { 2508 2509 // Get next character 2510 $char = mb_substr($txt, $i, 1, 'UTF-8'); 2511 2512 2513 if (isset($svg_font['glyphs'][$char])) { 2514 $d = $svg_font['glyphs'][$char]['d']; 2515 if (isset($svg_font['glyphs'][$char]['horiz-adv-x'])) { 2516 $horiz_adv_x = $svg_font['glyphs'][$char]['horiz-adv-x']; 2517 } else { 2518 $horiz_adv_x = $svg_font['horiz-adv-x']; 2519 } // missing glyph width 2520 } else { 2521 $d = $svg_font['d']; 2522 $horiz_adv_x = $svg_font['horiz-adv-x']; // missing glyph width 2523 } 2524 2525 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $d, $commands, PREG_SET_ORDER); 2526 $subpath_cmd .= sprintf('q %.4F 0 0 %.4F mPDF-AXS(%.4F) %.4F cm ', $scale, -$scale, ($x + $sw * $scale) * $this->kp, -$y * $this->kp); 2527 2528 $this->subPathInit = true; 2529 $this->pathBBox = [999999, 999999, -999999, -999999]; 2530 2531 foreach ($commands as $cmd) { 2532 2533 if (count($cmd) == 3 || (isset($cmd[2]) && $cmd[2] == '')) { 2534 list($tmp, $command, $arguments) = $cmd; 2535 } else { 2536 list($tmp, $command) = $cmd; 2537 $arguments = ''; 2538 } 2539 2540 $subpath_cmd .= $this->svgPath($command, $arguments); 2541 } 2542 2543 $subpath_cmd .= $op . ' Q '; 2544 2545 if ($this->pathBBox[2] == -1999998) { 2546 $this->pathBBox[2] = 100; 2547 } 2548 2549 if ($this->pathBBox[3] == -1999998) { 2550 $this->pathBBox[3] = 100; 2551 } 2552 2553 if ($this->pathBBox[0] == 999999) { 2554 $this->pathBBox[0] = 0; 2555 } 2556 2557 if ($this->pathBBox[1] == 999999) { 2558 $this->pathBBox[1] = 0; 2559 } 2560 2561 $sw += $horiz_adv_x; 2562 $i++; 2563 } 2564 2565 $sw *= $scale; // convert stringwidth to units 2566 // mPDF 5.7.4 2567 2568 $this->textlength = $sw; 2569 $this->texttotallength += $this->textlength; 2570 2571 $path_cmd = sprintf('q %s %s Tr %s %s ', $opacitystr, $render, $fillstr, $strokestr); 2572 $path_cmd .= $subpath_cmd; 2573 $path_cmd .= 'Q '; 2574 2575 unset($this->txt_data[0], $this->txt_data[1], $this->txt_data[2]); 2576 return $path_cmd; 2577 } 2578 2579 // select font 2580 $style .= ($current_style['font-weight'] == 'bold') ? 'B' : ''; 2581 $style .= ($current_style['font-style'] == 'italic') ? 'I' : ''; 2582 $size = $current_style['font-size'] * $this->kf; 2583 2584 $current_style['font-family'] = $this->mpdf->SetFont($current_style['font-family'], $style, $size, false); 2585 $this->mpdf->CurrentFont['fo'] = true; 2586 2587 $opacitystr = ''; 2588 // mPDF 6 2589 $fopacity = 1; 2590 2591 if (isset($current_style['fill-opacity'])) { 2592 if ($current_style['fill-opacity'] == 0) { 2593 $fopacity = 0; 2594 } elseif ($current_style['fill-opacity'] > 1) { 2595 $fopacity = 1; 2596 } elseif ($current_style['fill-opacity'] > 0) { 2597 $fopacity = $current_style['fill-opacity']; 2598 } elseif ($current_style['fill-opacity'] < 0) { 2599 $fopacity = 0; 2600 } 2601 } 2602 2603 $sopacity = 1; 2604 2605 if (isset($current_style['stroke-opacity'])) { 2606 if ($current_style['stroke-opacity'] == 0) { 2607 $sopacity = 0; 2608 } elseif ($current_style['stroke-opacity'] > 1) { 2609 $sopacity = 1; 2610 } elseif ($current_style['stroke-opacity'] > 0) { 2611 $sopacity = $current_style['stroke-opacity']; 2612 } elseif ($current_style['stroke-opacity'] < 0) { 2613 $sopacity = 0; 2614 } 2615 } 2616 2617 $gs = $this->mpdf->AddExtGState(['ca' => $fopacity, 'CA' => $sopacity, 'BM' => '/Normal']); 2618 $this->mpdf->extgstates[$gs]['fo'] = true; 2619 $opacitystr = sprintf(' /GS%d gs ', $gs); 2620 2621 $fillstr = ''; 2622 2623 if (isset($current_style['fill']) && $current_style['fill'] != 'none') { 2624 $col = $this->colorConverter->convert($current_style['fill'], $this->mpdf->PDFAXwarnings); 2625 $fillstr = $this->mpdf->SetFColor($col, true); 2626 $render = "0"; // Fill (only) 2627 } 2628 2629 $strokestr = ''; 2630 2631 if (isset($current_style['stroke-width']) && $current_style['stroke-width'] > 0 && $current_style['stroke'] != 'none') { 2632 $scol = $this->colorConverter->convert($current_style['stroke'], $this->mpdf->PDFAXwarnings); 2633 2634 if ($scol) { 2635 $strokestr .= $this->mpdf->SetDColor($scol, true) . ' '; 2636 } 2637 2638 $linewidth = $this->ConvertSVGSizePixels($current_style['stroke-width']); 2639 2640 if ($linewidth > 0) { 2641 $strokestr .= sprintf('%.3F w 1 J 1 j ', $linewidth * $this->kp); 2642 if ($render == -1) { 2643 $render = "1"; 2644 } // stroke only 2645 else { 2646 $render = "2"; 2647 } // fill and stroke 2648 } 2649 } 2650 2651 if ($render == -1) { 2652 return ''; 2653 } 2654 2655 $x = $this->txt_data[0]; // mPDF 5.7.4 2656 $y = $this->txt_data[1]; // mPDF 5.7.4 2657 $txt = $this->txt_data[2]; 2658 2659 $txt = preg_replace('/\f/', '', $txt); 2660 $txt = preg_replace('/\r/', '', $txt); 2661 $txt = preg_replace('/\n/', ' ', $txt); 2662 $txt = preg_replace('/\t/', ' ', $txt); 2663 $txt = preg_replace("/[ ]+/u", ' ', $txt); 2664 2665 if ($this->textjuststarted) { 2666 $txt = ltrim($txt); 2667 } // mPDF 5.7.4 2668 2669 $this->textjuststarted = false; // mPDF 5.7.4 2670 2671 $txt = $this->mpdf->purify_utf8_text($txt); 2672 2673 if ($this->mpdf->text_input_as_HTML) { 2674 $txt = $this->mpdf->all_entities_to_utf8($txt); 2675 } 2676 2677 if ($this->mpdf->usingCoreFont) { 2678 $txt = mb_convert_encoding($txt, $this->mpdf->mb_enc, 'UTF-8'); 2679 } 2680 2681 if (preg_match("/([" . $this->mpdf->pregRTLchars . "])/u", $txt)) { 2682 $this->mpdf->biDirectional = true; 2683 } 2684 2685 $textvar = 0; 2686 $save_OTLtags = $this->mpdf->OTLtags; 2687 $this->mpdf->OTLtags = []; 2688 2689 if ($this->mpdf->useKerning) { 2690 2691 if ($this->mpdf->CurrentFont['haskernGPOS']) { 2692 2693 if (isset($this->mpdf->OTLtags['Plus'])) { 2694 $this->mpdf->OTLtags['Plus'] .= ' kern'; 2695 } else { 2696 $this->mpdf->OTLtags['Plus'] = ' kern'; 2697 } 2698 2699 } else { 2700 $textvar = ($textvar | TextVars::FC_KERNING); 2701 } 2702 } 2703 2704 // Use OTL OpenType Table Layout - GSUB & GPOS 2705 if (isset($this->mpdf->CurrentFont['useOTL']) && $this->mpdf->CurrentFont['useOTL']) { 2706 $txt = $this->otl->applyOTL($txt, $this->mpdf->CurrentFont['useOTL']); 2707 $OTLdata = $this->otl->OTLdata; 2708 } 2709 2710 $this->mpdf->OTLtags = $save_OTLtags; 2711 2712 $this->mpdf->magic_reverse_dir($txt, $this->mpdf->directionality, $OTLdata); 2713 2714 $this->mpdf->CurrentFont['used'] = true; 2715 2716 $sw = $this->mpdf->GetStringWidth($txt, true, $OTLdata, $textvar); // also adds characters to subset 2717 2718 // mPDF 5.7.4 2719 $this->textlength = $sw * 1 / (25.4 / $this->mpdf->dpi); 2720 $this->texttotallength += $this->textlength; 2721 2722 $pdfx = $x * $this->kp; 2723 $pdfy = -$y * $this->kp; 2724 2725 $aixextra = sprintf(' /F%d %.3F Tf %s %s Tr %s %s ', $this->mpdf->CurrentFont['i'], $this->mpdf->FontSizePt, $opacitystr, $render, $fillstr, $strokestr); 2726 2727 $path_cmd = 'q 1 0 0 1 mPDF-AXS(0.00) 0 cm '; // Align X-shift 2728 $path_cmd .= $this->mpdf->Text($pdfx, $pdfy, $txt, $OTLdata, $textvar, $aixextra, 'SVG', true); 2729 $path_cmd .= " Q\n"; 2730 2731 unset($this->txt_data[0], $this->txt_data[1], $this->txt_data[2]); 2732 2733 if (isset($current_style['font-size-parent'])) { 2734 $this->mpdf->SetFontSize($current_style['font-size-parent']); 2735 } 2736 2737 } else { 2738 return ' '; 2739 } 2740 2741 // Reset font // mPDF 5.7.4 2742 $prev_style = $this->txt_style[count($this->txt_style) - 1]; 2743 $style = ''; 2744 $style .= ($prev_style['font-weight'] == 'bold') ? 'B' : ''; 2745 $style .= ($prev_style['font-style'] == 'italic') ? 'I' : ''; 2746 $size = $prev_style['font-size'] * $this->kf; 2747 $this->mpdf->SetFont($prev_style['font-family'], $style, $size, false); 2748 2749 return $path_cmd; 2750 } 2751 2752 function svgDefineTxtStyle($critere_style) 2753 { 2754 // get copy of current/default txt style, and modify it with supplied attributes 2755 $tmp = count($this->txt_style) - 1; 2756 $current_style = $this->txt_style[$tmp]; 2757 2758 if (isset($critere_style['style'])) { 2759 2760 if (preg_match('/fill:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) { 2761 $current_style['fill'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT); 2762 } else { 2763 $tmp = preg_replace("/(.*)fill:\s*([a-z0-9#_()]*|none)(.*)/i", "$2", $critere_style['style']); 2764 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2765 $current_style['fill'] = $tmp; 2766 } 2767 } 2768 2769 // mPDF 6 2770 if (preg_match("/[^-]opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m) || 2771 preg_match("/^opacity:\s*([a-z0-9.]*|none)/i", $critere_style['style'], $m)) { 2772 $current_style['fill-opacity'] = $m[1]; 2773 $current_style['stroke-opacity'] = $m[1]; 2774 } 2775 2776 $tmp = preg_replace("/(.*)fill-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 2777 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2778 $current_style['fill-opacity'] = $tmp; 2779 } 2780 2781 $tmp = preg_replace("/(.*)fill-rule:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 2782 if ($tmp != $critere_style['style'] && $tmp != $critere_style['style']) { 2783 $current_style['fill-rule'] = $tmp; 2784 } 2785 2786 if (preg_match('/stroke:\s*rgb\((\d+),\s*(\d+),\s*(\d+)\)/', $critere_style['style'], $m)) { 2787 $current_style['stroke'] = '#' . str_pad(dechex($m[1]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[2]), 2, "0", STR_PAD_LEFT) . str_pad(dechex($m[3]), 2, "0", STR_PAD_LEFT); 2788 } else { 2789 $tmp = preg_replace("/(.*)stroke:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 2790 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2791 $current_style['stroke'] = $tmp; 2792 } 2793 } 2794 2795 $tmp = preg_replace("/(.*)stroke-linecap:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 2796 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2797 $current_style['stroke-linecap'] = $tmp; 2798 } 2799 2800 $tmp = preg_replace("/(.*)stroke-linejoin:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 2801 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2802 $current_style['stroke-linejoin'] = $tmp; 2803 } 2804 2805 $tmp = preg_replace("/(.*)stroke-miterlimit:\s*([a-z0-9#]*|none)(.*)/i", "$2", $critere_style['style']); 2806 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2807 $current_style['stroke-miterlimit'] = $tmp; 2808 } 2809 2810 $tmp = preg_replace("/(.*)stroke-opacity:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 2811 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2812 $current_style['stroke-opacity'] = $tmp; 2813 } 2814 2815 $tmp = preg_replace("/(.*)stroke-width:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 2816 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2817 $current_style['stroke-width'] = $tmp; 2818 } 2819 2820 $tmp = preg_replace("/(.*)stroke-dasharray:\s*([a-z0-9., ]*|none)(.*)/i", "$2", $critere_style['style']); 2821 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2822 $current_style['stroke-dasharray'] = $tmp; 2823 } 2824 2825 $tmp = preg_replace("/(.*)stroke-dashoffset:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 2826 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2827 $current_style['stroke-dashoffset'] = $tmp; 2828 } 2829 2830 $tmp = preg_replace("/(.*)font-family:\s*([a-z0-9.\"' ,\-]*|none)(.*)/i", "$2", $critere_style['style']); 2831 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2832 $critere_style['font-family'] = $tmp; 2833 } 2834 2835 $tmp = preg_replace("/(.*)font-size:\s*([a-z0-9.]*|none)(.*)/i", "$2", $critere_style['style']); 2836 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2837 $critere_style['font-size'] = $tmp; 2838 } 2839 2840 $tmp = preg_replace("/(.*)font-weight:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']); 2841 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2842 $critere_style['font-weight'] = $tmp; 2843 } 2844 2845 $tmp = preg_replace("/(.*)font-style:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']); 2846 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2847 $critere_style['font-style'] = $tmp; 2848 } 2849 2850 $tmp = preg_replace("/(.*)font-variant:\s*([a-z0-9.]*|normal)(.*)/i", "$2", $critere_style['style']); 2851 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2852 $critere_style['font-variant'] = $tmp; 2853 } 2854 2855 $tmp = preg_replace("/(.*)text-anchor:\s*(start|middle|end)(.*)/i", "$2", $critere_style['style']); 2856 if ($tmp && $tmp != 'inherit' && $tmp != $critere_style['style']) { 2857 $critere_style['text-anchor'] = $tmp; 2858 } 2859 } 2860 2861 if (isset($critere_style['font'])) { 2862 // [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]?<'font-size'> [ / <'line-height'> ]? <'font-family'> ] 2863 2864 $tmp = preg_replace("/(.*)(italic|oblique)(.*)/i", "$2", $critere_style['font']); 2865 if ($tmp != $critere_style['font']) { 2866 if ($tmp == 'oblique') { 2867 $tmp = 'italic'; 2868 } 2869 $current_style['font-style'] = $tmp; 2870 } 2871 2872 $tmp = preg_replace("/(.*)(bold|bolder)(.*)/i", "$2", $critere_style['font']); 2873 if ($tmp != $critere_style['font']) { 2874 if ($tmp == 'bolder') { 2875 $tmp = 'bold'; 2876 } 2877 $current_style['font-weight'] = $tmp; 2878 } 2879 2880 $tmp = preg_replace("/(.*)(small\-caps)(.*)/i", "$2", $critere_style['font']); 2881 if ($tmp != $critere_style['font']) { 2882 $current_style['font-variant'] = $tmp; 2883 } 2884 2885 // select digits not followed by percent sign nor preceeded by forward slash 2886 $tmp = preg_replace("/(.*)\b(\d+)[\b|\/](.*)/i", "$2", $critere_style['font']); 2887 if ($tmp != $critere_style['font']) { 2888 $current_style['font-size'] = $this->ConvertSVGSizePts($tmp); 2889 $this->mpdf->SetFont('', '', $current_style['font-size'], false); 2890 } 2891 } 2892 2893 // mPDF 6 2894 if (isset($critere_style['opacity']) && $critere_style['opacity'] != 'inherit') { 2895 $current_style['fill-opacity'] = $critere_style['opacity']; 2896 $current_style['stroke-opacity'] = $critere_style['opacity']; 2897 } 2898 2899 // mPDF 6 2900 if (isset($critere_style['stroke-opacity']) && $critere_style['stroke-opacity'] != 'inherit') { 2901 $current_style['stroke-opacity'] = $critere_style['stroke-opacity']; 2902 } 2903 2904 // mPDF 6 2905 if (isset($critere_style['fill-opacity']) && $critere_style['fill-opacity'] != 'inherit') { 2906 $current_style['fill-opacity'] = $critere_style['fill-opacity']; 2907 } 2908 2909 if (isset($critere_style['fill']) && $critere_style['fill'] != 'inherit') { 2910 $current_style['fill'] = $critere_style['fill']; 2911 } 2912 2913 if (isset($critere_style['stroke']) && $critere_style['stroke'] != 'inherit') { 2914 $current_style['stroke'] = $critere_style['stroke']; 2915 } 2916 2917 if (isset($critere_style['stroke-width']) && $critere_style['stroke-width'] != 'inherit') { 2918 $current_style['stroke-width'] = $critere_style['stroke-width']; 2919 } 2920 2921 if (isset($critere_style['font-style']) && $critere_style['font-style'] != 'inherit') { 2922 if (strtolower($critere_style['font-style']) == 'oblique') { 2923 $critere_style['font-style'] = 'italic'; 2924 } 2925 $current_style['font-style'] = $critere_style['font-style']; 2926 } 2927 2928 if (isset($critere_style['font-weight']) && $critere_style['font-weight'] != 'inherit') { 2929 if (strtolower($critere_style['font-weight']) == 'bolder') { 2930 $critere_style['font-weight'] = 'bold'; 2931 } 2932 $current_style['font-weight'] = $critere_style['font-weight']; 2933 } 2934 2935 if (isset($critere_style['font-variant']) && $critere_style['font-variant'] != 'inherit') { 2936 $current_style['font-variant'] = $critere_style['font-variant']; 2937 } 2938 2939 if (isset($critere_style['font-size']) && $critere_style['font-size'] != 'inherit') { 2940 if (strpos($critere_style['font-size'], '%') !== false) { 2941 $current_style['font-size-parent'] = $current_style['font-size']; 2942 } 2943 $current_style['font-size'] = $this->ConvertSVGSizePts($critere_style['font-size']); 2944 $this->mpdf->SetFont('', '', $current_style['font-size'], false); 2945 } 2946 2947 if (isset($critere_style['font-family']) && $critere_style['font-family'] != 'inherit') { 2948 $v = $critere_style['font-family']; 2949 $aux_fontlist = explode(",", $v); 2950 $found = 0; 2951 2952 $svgfontstyle = 'R'; 2953 $svgfontstyle .= (isset($current_style['font-weight']) && $current_style['font-weight'] == 'bold') ? 'B' : ''; 2954 $svgfontstyle .= (isset($current_style['font-style']) && $current_style['font-style'] == 'italic') ? 'I' : ''; 2955 $svgfontstyle .= (isset($current_style['font-variant']) && $current_style['font-variant'] == 'small-caps') ? 'S' : ''; 2956 2957 foreach ($aux_fontlist as $f) { 2958 $fonttype = trim($f); 2959 $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype); 2960 $fonttype = preg_replace('/ /', '', $fonttype); 2961 $v = strtolower(trim($fonttype)); 2962 if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) { 2963 $v = $this->mpdf->fonttrans[$v]; 2964 } 2965 if ((!$this->mpdf->usingCoreFont && in_array($v, $this->mpdf->available_unifonts)) || 2966 ($this->mpdf->usingCoreFont && in_array($v, ['courier', 'times', 'helvetica', 'arial'])) || 2967 in_array($v, ['sjis', 'uhc', 'big5', 'gb']) || isset($this->svg_font[$v][$svgfontstyle])) { // mPDF 6 2968 $current_style['font-family'] = $v; 2969 $found = 1; 2970 break; 2971 } 2972 } 2973 2974 if (!$found) { 2975 foreach ($aux_fontlist as $f) { 2976 $fonttype = trim($f); 2977 $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype); 2978 $fonttype = preg_replace('/ /', '', $fonttype); 2979 $v = strtolower(trim($fonttype)); 2980 if (isset($this->mpdf->fonttrans[$v]) && $this->mpdf->fonttrans[$v]) { 2981 $v = $this->mpdf->fonttrans[$v]; 2982 } 2983 if (in_array($v, $this->mpdf->sans_fonts) || in_array($v, $this->mpdf->serif_fonts) || in_array($v, $this->mpdf->mono_fonts) || isset($this->svg_font[$v][$svgfontstyle])) { // mPDF 6 2984 $current_style['font-family'] = $v; 2985 break; 2986 } 2987 } 2988 } 2989 } 2990 2991 if (isset($critere_style['text-anchor']) && $critere_style['text-anchor'] != 'inherit') { 2992 $current_style['text-anchor'] = $critere_style['text-anchor']; 2993 } 2994 2995 // add current style to text style array (will remove it later after writing text to svg_string) 2996 array_push($this->txt_style, $current_style); 2997 } 2998 2999 function svgAddGradient($id, $array_gradient) 3000 { 3001 3002 $this->svg_gradient[$id] = $array_gradient; 3003 } 3004 3005 function svgWriteString($content) 3006 { 3007 $this->svg_string .= $content; 3008 } 3009 3010 /** 3011 * SVGs made with Adobe Illustrator use a style tag and classes instead of inline styles 3012 * See: https://github.com/mpdf/mpdf/issues/450 3013 * 3014 * This function brutally copies the styles inline 3015 * ( Currently only looks for classes as a selector ) 3016 * 3017 * @param string $data svg contents 3018 * @return string svg contents 3019 * @author Antonio Norman - softcodex.ch 3020 */ 3021 function mergeStyles($data) 3022 { 3023 $xml = new \DOMDocument(); 3024 if (!$xml->loadXML($data, LIBXML_NOERROR)) { 3025 return $data; 3026 } 3027 3028 // Check it's an SVG 3029 $svgNode = $xml->getElementsByTagName('svg'); 3030 if ($svgNode->length === 0) { 3031 return $data; 3032 } 3033 3034 // Find the style node 3035 $styles = []; 3036 /** @var $styleNode \DOMNode */ 3037 foreach ($svgNode->item(0)->getElementsByTagName('style') as $styleNode) { 3038 3039 preg_match_all('/(\.[^{]+)\s*\{\s*([^}]+)\s*}/m', $styleNode->nodeValue, $matches, PREG_SET_ORDER); 3040 foreach ($matches as $cssBlock) { 3041 $css = preg_replace('/\s{2,}/', ' ', $cssBlock[2]); // Clean spaces or new lines 3042 $selector = trim($cssBlock[1]); 3043 3044 $styles[$selector] = isset($styles[$cssBlock[1]]) ? 3045 $styles[$selector] . ' ' . $css : // Append if the selector is already defined 3046 $css; 3047 } 3048 } 3049 3050 if (empty($styles)) { 3051 return $data; 3052 } 3053 3054 // Recursively loop the nodes inserting the styles inline 3055 $setStylesInline = function (\DOMNode $xml) use ($styles, &$setStylesInline) { 3056 // Apply the styles to the elements 3057 foreach ($xml->childNodes as $node) { 3058 3059 if ($node->hasChildNodes()) { 3060 $setStylesInline($node); 3061 } 3062 3063 if (!$node instanceof \DOMElement) { 3064 continue; 3065 } 3066 3067 // Check the node has the a class with a style 3068 if (!$node->hasAttribute('class')) { 3069 continue; 3070 } 3071 3072 // Allow for class=" class1 class2 " 3073 $classes = explode(' ', $node->getAttribute('class')); 3074 3075 foreach ($classes as $class) { 3076 3077 $class = '.' . trim($class); 3078 if (!empty($class) && isset($styles[$class])) { 3079 3080 $style = $node->hasAttribute('style') ? 3081 $styles[$class] . ' ' . $node->getAttribute('style') : 3082 $styles[$class]; 3083 3084 $node->setAttribute('style', $style); 3085 } 3086 } 3087 } 3088 }; 3089 3090 $setStylesInline($xml); 3091 3092 return $xml->saveXML(); 3093 } 3094 3095 /** 3096 * analise le svg et renvoie aux fonctions precedente our le traitement 3097 */ 3098 function ImageSVG($data) 3099 { 3100 $data = preg_replace('/^.*?<svg([> ])/is', '<svg\\1', $data); // mPDF 5.7.4 3101 $data = preg_replace('/<!--.*?-->/is', '', $data); // mPDF 5.7.4 3102 3103 // Converts < to < when not a tag 3104 $data = preg_replace('/<([^!?\/a-zA-Z_:])/i', '<\\1', $data); // mPDF 5.7.4 3105 3106 $data = $this->mergeStyles($data); 3107 3108 if ($this->mpdf->svgAutoFont) { 3109 $data = $this->markScriptToLang($data); 3110 } 3111 3112 $this->svg_info = []; 3113 $last_gradid = ''; // mPDF 6 3114 $last_svg_fontid = ''; // mPDF 6 3115 $last_svg_fontdefw = ''; // mPDF 6 3116 $last_svg_fontstyle = ''; // mPDF 6 3117 3118 if (preg_match('/<!ENTITY/si', $data)) { 3119 // Get User-defined entities 3120 preg_match_all('/<!ENTITY\s+([a-z]+)\s+\"(.*?)\">/si', $data, $ent); 3121 // Replace entities 3122 for ($i = 0; $i < count($ent[0]); $i++) { 3123 $data = preg_replace('/&' . preg_quote($ent[1][$i], '/') . ';/is', $ent[2][$i], $data); 3124 } 3125 } 3126 3127 if (preg_match('/xlink:href\s*=/si', $data)) { 3128 3129 // GRADIENTS 3130 // Get links 3131 preg_match_all('/(<(linearGradient|radialgradient)[^>]*)xlink:href\s*=\s*["\']#(.*?)["\'](.*?)\/>/si', $data, $links); 3132 if (count($links[0])) { 3133 $links[5] = []; 3134 } 3135 3136 // Delete links from data - keeping in $links 3137 for ($i = 0; $i < count($links[0]); $i++) { 3138 $links[5][$i] = 'tmpLink' . random_int(100000, 9999999); 3139 $data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', '<MYLINKS' . $links[5][$i] . '>', $data); 3140 } 3141 3142 // Get targets 3143 preg_match_all('/<(linearGradient|radialgradient)([^>]*)id\s*=\s*["\'](.*?)["\'](.*?)>(.*?)<\/(linearGradient|radialgradient)>/si', $data, $m); 3144 $targets = []; 3145 $stops = []; 3146 3147 // keeping in $targets 3148 for ($i = 0; $i < count($m[0]); $i++) { 3149 $stops[$m[3][$i]] = $m[5][$i]; 3150 } 3151 3152 // Add back links this time as targets (gradients) 3153 for ($i = 0; $i < count($links[0]); $i++) { 3154 $def = $links[1][$i] . ' ' . $links[4][$i] . '>' . $stops[$links[3][$i]] . '</' . $links[2][$i] . '>'; 3155 $data = preg_replace('/<MYLINKS' . $links[5][$i] . '>/is', $def, $data); 3156 } 3157 3158 // mPDF 5.7.4 3159 // <TREF> 3160 preg_match_all('/<tref ([^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)\/>/si', $data, $links); 3161 for ($i = 0; $i < count($links[0]); $i++) { 3162 // Get the item to use from defs 3163 $insert = ''; 3164 if (preg_match('/<text [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>(.*?)<\/text>/si', $data, $m)) { 3165 $insert = $m[1]; 3166 } 3167 if ($insert) { 3168 $data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $insert, $data); 3169 } 3170 } 3171 3172 // mPDF 5.7.2 3173 // <USE> 3174 preg_match_all('/<use( [^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)\/>/si', $data, $links); 3175 for ($i = 0; $i < count($links[0]); $i++) { 3176 // Get the item to use from defs 3177 $insert = ''; 3178 if (preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*\/>/si', $data, $m)) { 3179 $insert = $m[0]; 3180 } 3181 if (!$insert && preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\']/si', $data, $m)) { 3182 if (preg_match('/<' . $m[1] . '[^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>.*?<\/' . $m[1] . '>/si', $data, $m)) { 3183 $insert = $m[0]; 3184 } 3185 } 3186 3187 if ($insert) { 3188 $inners = $links[1][$i] . ' ' . $links[3][$i]; 3189 3190 // Change x,y coords to translate() 3191 if (preg_match('/\sy\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3192 $y = $m[1]; 3193 } else { 3194 $y = 0; 3195 } 3196 3197 if (preg_match('/\sx\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3198 $x = $m[1]; 3199 } else { 3200 $x = 0; 3201 } 3202 3203 if ($x || $y) { 3204 3205 $inners = preg_replace('/(y|x)\s*=\s*["\']([^>]*?)["\']/', '', $inners); 3206 if (preg_match('/transform\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3207 3208 if (preg_match('/translate\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/', $m[1], $mm)) { 3209 $transform = $m[1]; // transform="...." 3210 $x += $mm[1]; 3211 $y += $mm[2]; 3212 $transform = preg_replace('/' . preg_quote($mm[0], '/') . '/', '', $transform); 3213 $transform = 'transform="' . $transform . ' translate(' . $x . ', ' . $y . ')"'; 3214 $inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', $transform, $inners); 3215 } else { 3216 $inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', 'transform="' . $m[1] . ' translate(' . $x . ', ' . $y . ')"', $inners); 3217 } 3218 3219 } else { 3220 $inners .= ' transform="translate(' . $x . ', ' . $y . ')"'; 3221 } 3222 } 3223 } 3224 3225 $replacement = '<g ' . $inners . '>' . $insert . '</g>'; 3226 $data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $replacement, $data); 3227 } 3228 3229 preg_match_all('/<use( [^>]*)xlink:href\s*=\s*["\']#([^>]*?)["\']([^>]*)>\s*<\/use>/si', $data, $links); 3230 for ($i = 0; $i < count($links[0]); $i++) { 3231 3232 // Get the item to use from defs 3233 $insert = ''; 3234 if (preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*\/>/si', $data, $m)) { 3235 $insert = $m[0]; 3236 } 3237 3238 if (!$insert && preg_match('/<([a-zA-Z]*) [^>]*id\s*=\s*["\']' . $links[2][$i] . '["\']/si', $data, $m)) { 3239 if (preg_match('/<' . $m[1] . '[^>]*id\s*=\s*["\']' . $links[2][$i] . '["\'][^>]*>.*?<\/' . $m[1] . '>/si', $data, $m)) { 3240 $insert = $m[0]; 3241 } 3242 } 3243 3244 if ($insert) { 3245 $inners = $links[1][$i] . ' ' . $links[3][$i]; 3246 3247 // Change x,y coords to translate() 3248 if (preg_match('/\sy\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3249 $y = $m[1]; 3250 } else { 3251 $y = 0; 3252 } 3253 3254 if (preg_match('/\sx\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3255 $x = $m[1]; 3256 } else { 3257 $x = 0; 3258 } 3259 3260 if ($x || $y) { 3261 $inners = preg_replace('/(y|x)\s*=\s*["\']([^>]*?)["\']/', '', $inners); 3262 if (preg_match('/transform\s*=\s*["\']([^>]*?)["\']/', $inners, $m)) { 3263 if (preg_match('/translate\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/', $m[1], $mm)) { 3264 $transform = $m[1]; // transform="...." 3265 $x += $mm[1]; 3266 $y += $mm[2]; 3267 $transform = preg_replace('/' . preg_quote($mm[0], '/') . '/', '', $transform); 3268 $transform = 'transform="' . $transform . ' translate(' . $x . ', ' . $y . ')"'; 3269 $inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', $transform, $inners); 3270 } else { 3271 $inners = preg_replace('/' . preg_quote($m[0], '/') . '/is', 'transform="' . $m[1] . ' translate(' . $x . ', ' . $y . ')"', $inners); 3272 } 3273 } else { 3274 $inners .= ' transform="translate(' . $x . ', ' . $y . ')"'; 3275 } 3276 } 3277 3278 $replacement = '<g ' . $inners . '>' . $insert . '</g>'; 3279 $data = preg_replace('/' . preg_quote($links[0][$i], '/') . '/is', $replacement, $data); 3280 } 3281 } 3282 } 3283 3284 // Removes <pattern> 3285 $data = preg_replace('/<pattern.*?<\/pattern>/is', '', $data); 3286 3287 // Removes <marker> 3288 $data = preg_replace('/<marker.*?<\/marker>/is', '', $data); 3289 3290 $this->svg_info['data'] = $data; 3291 $this->svg_string = ''; 3292 3293 $svg2pdf_xml = ''; 3294 3295 // Don't output stuff inside <defs> 3296 $this->inDefs = false; 3297 3298 $svg2pdf_xml_parser = xml_parser_create("utf-8"); 3299 3300 xml_parser_set_option($svg2pdf_xml_parser, XML_OPTION_CASE_FOLDING, false); 3301 3302 xml_set_element_handler( 3303 $svg2pdf_xml_parser, 3304 [$this, 'xml_svg2pdf_start'], 3305 [$this, 'xml_svg2pdf_end'] 3306 ); 3307 3308 xml_set_character_data_handler( 3309 $svg2pdf_xml_parser, 3310 [$this, 'characterData'] 3311 ); 3312 3313 xml_parse($svg2pdf_xml_parser, $data); 3314 3315 if ($this->svg_error) { 3316 return false; 3317 } else { 3318 return [ 3319 'x' => $this->svg_info['x'] * $this->kp, 3320 'y' => -$this->svg_info['y'] * $this->kp, 3321 'w' => $this->svg_info['w'] * $this->kp, 3322 'h' => -$this->svg_info['h'] * $this->kp, 3323 'data' => $this->svg_string, 3324 ]; 3325 } 3326 } 3327 3328 // AUTOFONT ========================= 3329 /** @todo reuse as much code from Mpdf::markScriptToLang as possible */ 3330 function markScriptToLang($html) 3331 { 3332 if ($this->mpdf->onlyCoreFonts) { 3333 return $html; 3334 } 3335 3336 $n = ''; 3337 $a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE); 3338 foreach ($a as $i => $e) { 3339 if ($i % 2 == 0) { 3340 $e = UtfString::strcode2utf($e); 3341 $e = $this->mpdf->lesser_entity_decode($e); 3342 3343 $earr = $this->mpdf->UTF8StringToArray($e, false); 3344 3345 $scriptblock = 0; 3346 $scriptblocks = []; 3347 $scriptblocks[0] = 0; 3348 $chardata = []; 3349 $subchunk = 0; 3350 $charctr = 0; 3351 foreach ($earr as $char) { 3352 $ucd_record = Ucdn::get_ucd_record($char); 3353 $sbl = $ucd_record[6]; 3354 3355 if ($sbl && $sbl != 40 && $sbl != 102) { 3356 if ($scriptblock == 0) { 3357 $scriptblock = $sbl; 3358 $scriptblocks[$subchunk] = $scriptblock; 3359 } elseif ($scriptblock > 0 && $scriptblock != $sbl) { 3360 // NEW (non-common) Script encountered in this chunk. 3361 // Start a new subchunk 3362 $subchunk++; 3363 $scriptblock = $sbl; 3364 $charctr = 0; 3365 $scriptblocks[$subchunk] = $scriptblock; 3366 } 3367 } 3368 3369 $chardata[$subchunk][$charctr]['script'] = $sbl; 3370 $chardata[$subchunk][$charctr]['uni'] = $char; 3371 $charctr++; 3372 } 3373 3374 // If scriptblock[x] = common & non-baseScript 3375 // and scriptblock[x+1] = baseScript 3376 // Move common script from end of x to start of x+1 3377 for ($sch = 0; $sch < $subchunk; $sch++) { 3378 if ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->mpdf->baseScript && $scriptblocks[$sch + 1] == $this->mpdf->baseScript) { 3379 $end = count($chardata[$sch]) - 1; 3380 while ($chardata[$sch][$end]['script'] == 0 && $end > 1) { // common script 3381 $tmp = array_pop($chardata[$sch]); 3382 array_unshift($chardata[$sch + 1], $tmp); 3383 $end--; 3384 } 3385 } 3386 } 3387 3388 $o = ''; 3389 for ($sch = 0; $sch <= $subchunk; $sch++) { 3390 if (isset($chardata[$sch])) { 3391 $s = ''; 3392 for ($j = 0; $j < count($chardata[$sch]); $j++) { 3393 $s .= UtfString::code2utf($chardata[$sch][$j]['uni']); 3394 } 3395 // ZZZ99 Undo lesser_entity_decode as above - but only for <>& 3396 $s = str_replace("&", "&", $s); 3397 $s = str_replace("<", "<", $s); 3398 $s = str_replace(">", ">", $s); 3399 3400 if (substr($a[$i - 1], 0, 5) != '<text' && substr($a[$i - 1], 0, 5) != '<tspa') { 3401 continue; 3402 } // <tspan> or <text> only 3403 3404 $lang = ''; 3405 // Check Vietnamese if Latin script - even if Basescript 3406 if ($scriptblocks[$sch] == Ucdn::SCRIPT_LATIN && $this->mpdf->autoVietnamese && preg_match("/([" . $this->scriptToLanguage->getLanguageDelimiters('viet') . "])/u", $s)) { 3407 $lang = "vi"; 3408 } // Check Arabic for different languages if Arabic script - even if Basescript 3409 elseif ($scriptblocks[$sch] == Ucdn::SCRIPT_ARABIC && $this->mpdf->autoArabic) { 3410 if (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('sindhi') . "]/u", $s)) { 3411 $lang = "sd"; 3412 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('urdu') . "]/u", $s)) { 3413 $lang = "ur"; 3414 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('pashto') . "]/u", $s)) { 3415 $lang = "ps"; 3416 } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('persian') . "]/u", $s)) { 3417 $lang = "fa"; 3418 } elseif ($this->mpdf->baseScript != Ucdn::SCRIPT_ARABIC && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) { 3419 $lang = "'." . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . "'"; 3420 } 3421 } // Identify Script block if not Basescript, and mark up as language 3422 elseif ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->mpdf->baseScript && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) { 3423 $lang = $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]); 3424 } 3425 if ($lang) { 3426 $o .= '<tspan lang="' . $lang . '">' . $s . '</tspan>'; 3427 } else { 3428 $o .= $s; 3429 } 3430 } 3431 } 3432 3433 $a[$i] = $o; 3434 } else { 3435 $a[$i] = '<' . $e . '>'; 3436 } 3437 } 3438 $n = implode('', $a); 3439 return $n; 3440 } 3441 3442 function xml_svg2pdf_start($parser, $name, $attribs) 3443 { 3444 global $last_gradid, $last_svg_fontid, $last_svg_fontdefw, $last_svg_fontstyle; // mPDF 6 3445 3446 // mPDF 6 3447 if (strtolower($name) == 'font') { 3448 $last_svg_fontid = ''; 3449 3450 if (isset($attribs['horiz-adv-x']) && $attribs['horiz-adv-x']) { 3451 $last_svg_fontdefw = $attribs['horiz-adv-x']; 3452 } 3453 3454 return; 3455 } elseif (strtolower($name) == 'font-face') { // mPDF 6 3456 $last_svg_fontstyle = 'R'; 3457 $last_svg_fontstyle .= (isset($attribs['font-weight']) && $attribs['font-weight'] == 'bold') ? 'B' : ''; 3458 $last_svg_fontstyle .= (isset($attribs['font-style']) && $attribs['font-style'] == 'italic') ? 'I' : ''; 3459 $last_svg_fontstyle .= (isset($attribs['font-variant']) && $attribs['font-variant'] == 'small-caps') ? 'S' : ''; 3460 3461 if (isset($attribs['font-family']) && $attribs['font-family']) { 3462 3463 $tmp_svg_font = [ 3464 'units-per-em' => (isset($attribs['units-per-em']) ? $attribs['units-per-em'] : ''), 3465 'd' => '', 3466 'glyphs' => [] 3467 ]; 3468 3469 $last_svg_fontid = strtolower($attribs['font-family']); 3470 3471 if ($last_svg_fontdefw) { 3472 $tmp_svg_font['horiz-adv-x'] = $last_svg_fontdefw; 3473 } else { 3474 $tmp_svg_font['horiz-adv-x'] = 500; 3475 } 3476 3477 $this->svg_font[$last_svg_fontid][$last_svg_fontstyle] = $tmp_svg_font; 3478 } 3479 3480 return; 3481 } elseif (strtolower($name) == 'missing-glyph') { // mPDF 6 3482 3483 if ($last_svg_fontid && isset($attribs['horiz-adv-x'])) { 3484 $this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['horiz-adv-x'] = (isset($attribs['horiz-adv-x']) ? $attribs['horiz-adv-x'] : ''); 3485 $this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['d'] = (isset($attribs['d']) ? $attribs['d'] : ''); 3486 } 3487 3488 return; 3489 3490 } elseif (strtolower($name) == 'glyph') { // mPDF 6 3491 3492 if ($last_svg_fontid && isset($attribs['unicode'])) { 3493 $this->svg_font[$last_svg_fontid][$last_svg_fontstyle]['glyphs'][$attribs['unicode']] = [ 3494 'horiz-adv-x' => (isset($attribs['horiz-adv-x']) ? $attribs['horiz-adv-x'] : $last_svg_fontdefw), 3495 'd' => (isset($attribs['d']) ? $attribs['d'] : ''), 3496 ]; 3497 } 3498 return; 3499 3500 } elseif (strtolower($name) == 'lineargradient') { // mPDF 5.7.2 3501 3502 $tmp_gradient = [ 3503 'type' => 'linear', 3504 'transform' => (isset($attribs['gradientTransform']) ? $attribs['gradientTransform'] : ''), 3505 'units' => (isset($attribs['gradientUnits']) ? $attribs['gradientUnits'] : ''), 3506 'spread' => (isset($attribs['spreadMethod']) ? $attribs['spreadMethod'] : ''), 3507 'color' => [] 3508 ]; 3509 3510 if (isset($attribs['x1'])) { 3511 $tmp_gradient['info']['x1'] = $attribs['x1']; 3512 } 3513 3514 if (isset($attribs['y1'])) { 3515 $tmp_gradient['info']['y1'] = $attribs['y1']; 3516 } 3517 3518 if (isset($attribs['x2'])) { 3519 $tmp_gradient['info']['x2'] = $attribs['x2']; 3520 } 3521 3522 if (isset($attribs['y2'])) { 3523 $tmp_gradient['info']['y2'] = $attribs['y2']; 3524 } 3525 3526 $last_gradid = $attribs['id']; 3527 $this->svgAddGradient($attribs['id'], $tmp_gradient); 3528 3529 return; 3530 3531 } elseif (strtolower($name) == 'radialgradient') { 3532 3533 $tmp_gradient = [ 3534 'type' => 'radial', 3535 'transform' => (isset($attribs['gradientTransform']) ? $attribs['gradientTransform'] : ''), 3536 'units' => (isset($attribs['gradientUnits']) ? $attribs['gradientUnits'] : ''), 3537 'spread' => (isset($attribs['spreadMethod']) ? $attribs['spreadMethod'] : ''), 3538 'color' => [] 3539 ]; 3540 3541 if (isset($attribs['cx'])) { 3542 $tmp_gradient['info']['x0'] = $attribs['cx']; 3543 } 3544 3545 if (isset($attribs['cy'])) { 3546 $tmp_gradient['info']['y0'] = $attribs['cy']; 3547 } 3548 3549 if (isset($attribs['fx'])) { 3550 $tmp_gradient['info']['x1'] = $attribs['fx']; 3551 } 3552 3553 if (isset($attribs['fy'])) { 3554 $tmp_gradient['info']['y1'] = $attribs['fy']; 3555 } 3556 3557 if (isset($attribs['r'])) { 3558 $tmp_gradient['info']['r'] = $attribs['r']; 3559 } 3560 3561 $last_gradid = $attribs['id']; 3562 $this->svgAddGradient($attribs['id'], $tmp_gradient); 3563 3564 return; 3565 3566 } elseif (strtolower($name) == 'stop') { 3567 3568 if (!$last_gradid) { 3569 return; 3570 } 3571 3572 $color = '#000000'; 3573 if (isset($attribs['style']) and preg_match('/stop-color:\s*([^;]*)/i', $attribs['style'], $m)) { 3574 $color = trim($m[1]); 3575 } elseif (isset($attribs['stop-color']) && $attribs['stop-color']) { 3576 $color = $attribs['stop-color']; 3577 } 3578 3579 $col = $this->colorConverter->convert($color, $this->mpdf->PDFAXwarnings); 3580 if (!$col) { 3581 $col = $this->colorConverter->convert('#000000', $this->mpdf->PDFAXwarnings); 3582 } // In case "transparent" or "inherit" returned 3583 3584 if ($col[0] == 3 || $col[0] == 5) { // RGB 3585 $color_final = sprintf('%.3F %.3F %.3F', ord($col[1]) / 255, ord($col[2]) / 255, ord($col[3]) / 255); 3586 $this->svg_gradient[$last_gradid]['colorspace'] = 'RGB'; 3587 } elseif ($col[0] == 4 || $col[0] == 6) { // CMYK 3588 $color_final = sprintf('%.3F %.3F %.3F %.3F', ord($col[1]) / 100, ord($col[2]) / 100, ord($col[3]) / 100, ord($col[4]) / 100); 3589 $this->svg_gradient[$last_gradid]['colorspace'] = 'CMYK'; 3590 } elseif ($col[0] == 1) { // Grayscale 3591 $color_final = sprintf('%.3F', ord($col[1]) / 255); 3592 $this->svg_gradient[$last_gradid]['colorspace'] = 'Gray'; 3593 } 3594 3595 $stop_opacity = 1; 3596 3597 if (isset($attribs['style']) and preg_match('/stop-opacity:\s*([0-9.]*)/i', $attribs['style'], $m)) { 3598 $stop_opacity = $m[1]; 3599 } elseif (isset($attribs['stop-opacity'])) { 3600 $stop_opacity = $attribs['stop-opacity']; 3601 } elseif ($col[0] == 5) { // RGBa 3602 $stop_opacity = ord($col[4] / 100); 3603 } elseif ($col[0] == 6) { // CMYKa 3604 $stop_opacity = ord($col[5] / 100); 3605 } 3606 3607 $tmp_color = [ 3608 'color' => $color_final, 3609 'offset' => (isset($attribs['offset']) ? $attribs['offset'] : ''), 3610 'opacity' => $stop_opacity 3611 ]; 3612 array_push($this->svg_gradient[$last_gradid]['color'], $tmp_color); 3613 3614 return; 3615 } 3616 3617 if ($this->inDefs) { 3618 return; 3619 } 3620 3621 $this->xbase = 0; 3622 $this->ybase = 0; 3623 3624 switch (strtolower($name)) { 3625 3626 // Don't output stuff inside <defs> 3627 case 'defs': 3628 $this->inDefs = true; 3629 return; 3630 3631 case 'svg': 3632 $this->svgOffset($attribs); 3633 break; 3634 3635 case 'path': 3636 3637 $path = Arrays::get($attribs, 'd', ''); 3638 preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $path, $commands, PREG_SET_ORDER); 3639 $path_cmd = ''; 3640 $this->subPathInit = true; 3641 $this->pathBBox = [999999, 999999, -999999, -999999]; 3642 3643 foreach ($commands as $c) { 3644 if ((isset($c) && count($c) == 3) || (isset($c[2]) && $c[2] == '')) { 3645 list($tmp, $command, $arguments) = $c; 3646 } else { 3647 list($tmp, $command) = $c; 3648 $arguments = ''; 3649 } 3650 3651 $path_cmd .= $this->svgPath($command, $arguments); 3652 } 3653 3654 if ($this->pathBBox[2] == -1999998) { 3655 $this->pathBBox[2] = 100; 3656 } 3657 3658 if ($this->pathBBox[3] == -1999998) { 3659 $this->pathBBox[3] = 100; 3660 } 3661 3662 if ($this->pathBBox[0] == 999999) { 3663 $this->pathBBox[0] = 0; 3664 } 3665 3666 if ($this->pathBBox[1] == 999999) { 3667 $this->pathBBox[1] = 0; 3668 } 3669 3670 $critere_style = $attribs; 3671 unset($critere_style['d']); 3672 $path_style = $this->svgDefineStyle($critere_style); 3673 3674 break; 3675 3676 case 'rect': 3677 if (!isset($attribs['x'])) { 3678 $attribs['x'] = 0; 3679 } 3680 3681 if (!isset($attribs['y'])) { 3682 $attribs['y'] = 0; 3683 } 3684 3685 if (!isset($attribs['rx'])) { 3686 $attribs['rx'] = 0; 3687 } 3688 3689 if (!isset($attribs['ry'])) { 3690 $attribs['ry'] = 0; 3691 } 3692 3693 $arguments = []; 3694 3695 if (isset($attribs['x'])) { 3696 $arguments['x'] = $attribs['x']; 3697 } 3698 3699 if (isset($attribs['y'])) { 3700 $arguments['y'] = $attribs['y']; 3701 } 3702 3703 if (isset($attribs['width'])) { 3704 $arguments['w'] = $attribs['width']; 3705 } 3706 3707 if (isset($attribs['height'])) { 3708 $arguments['h'] = $attribs['height']; 3709 } 3710 3711 if (isset($attribs['rx'])) { 3712 $arguments['rx'] = $attribs['rx']; 3713 } 3714 3715 if (isset($attribs['ry'])) { 3716 $arguments['ry'] = $attribs['ry']; 3717 } 3718 3719 $path_cmd = $this->svgRect($arguments); 3720 $critere_style = $attribs; 3721 unset($critere_style['x'], $critere_style['y'], $critere_style['rx'], $critere_style['ry'], $critere_style['height'], $critere_style['width']); 3722 $path_style = $this->svgDefineStyle($critere_style); 3723 3724 break; 3725 3726 case 'circle': 3727 3728 if (!isset($attribs['cx'])) { 3729 $attribs['cx'] = 0; 3730 } 3731 3732 if (!isset($attribs['cy'])) { 3733 $attribs['cy'] = 0; 3734 } 3735 3736 $arguments = []; 3737 3738 if (isset($attribs['cx'])) { 3739 $arguments['cx'] = $attribs['cx']; 3740 } 3741 3742 if (isset($attribs['cy'])) { 3743 $arguments['cy'] = $attribs['cy']; 3744 } 3745 3746 if (isset($attribs['r'])) { 3747 $arguments['rx'] = $attribs['r']; 3748 } 3749 3750 if (isset($attribs['r'])) { 3751 $arguments['ry'] = $attribs['r']; 3752 } 3753 3754 $path_cmd = $this->svgEllipse($arguments); 3755 $critere_style = $attribs; 3756 unset($critere_style['cx'], $critere_style['cy'], $critere_style['r']); 3757 $path_style = $this->svgDefineStyle($critere_style); 3758 3759 break; 3760 3761 case 'ellipse': 3762 3763 if (!isset($attribs['cx'])) { 3764 $attribs['cx'] = 0; 3765 } 3766 3767 if (!isset($attribs['cy'])) { 3768 $attribs['cy'] = 0; 3769 } 3770 3771 $arguments = []; 3772 3773 if (isset($attribs['cx'])) { 3774 $arguments['cx'] = $attribs['cx']; 3775 } 3776 3777 if (isset($attribs['cy'])) { 3778 $arguments['cy'] = $attribs['cy']; 3779 } 3780 3781 if (isset($attribs['rx'])) { 3782 $arguments['rx'] = $attribs['rx']; 3783 } 3784 3785 if (isset($attribs['ry'])) { 3786 $arguments['ry'] = $attribs['ry']; 3787 } 3788 3789 $path_cmd = $this->svgEllipse($arguments); 3790 $critere_style = $attribs; 3791 unset($critere_style['cx'], $critere_style['cy'], $critere_style['rx'], $critere_style['ry']); 3792 $path_style = $this->svgDefineStyle($critere_style); 3793 3794 break; 3795 3796 case 'line': 3797 3798 $arguments = []; 3799 $arguments[0] = (isset($attribs['x1']) ? $attribs['x1'] : ''); 3800 $arguments[1] = (isset($attribs['y1']) ? $attribs['y1'] : ''); 3801 $arguments[2] = (isset($attribs['x2']) ? $attribs['x2'] : ''); 3802 $arguments[3] = (isset($attribs['y2']) ? $attribs['y2'] : ''); 3803 $path_cmd = $this->svgPolyline($arguments, false); 3804 $critere_style = $attribs; 3805 unset($critere_style['x1'], $critere_style['y1'], $critere_style['x2'], $critere_style['y2']); 3806 $path_style = $this->svgDefineStyle($critere_style); 3807 3808 break; 3809 3810 case 'polyline': 3811 3812 $path = $attribs['points']; 3813 preg_match_all('/[0-9\-\.]*/', $path, $tmp, PREG_SET_ORDER); 3814 $arguments = []; 3815 3816 for ($i = 0; $i < count($tmp); $i++) { 3817 if ($tmp[$i][0] != '') { 3818 array_push($arguments, $tmp[$i][0]); 3819 } 3820 } 3821 3822 $path_cmd = $this->svgPolyline($arguments); 3823 $critere_style = $attribs; 3824 unset($critere_style['points']); 3825 $path_style = $this->svgDefineStyle($critere_style); 3826 3827 break; 3828 3829 case 'polygon': 3830 3831 $path = $attribs['points']; 3832 preg_match_all('/([\-]*[0-9\.]+)/', $path, $tmp); 3833 $arguments = []; 3834 3835 for ($i = 0; $i < count($tmp[0]); $i++) { 3836 if ($tmp[0][$i] != '') { 3837 array_push($arguments, $tmp[0][$i]); 3838 } 3839 } 3840 3841 $path_cmd = $this->svgPolygon($arguments); 3842 // definition du style de la forme: 3843 $critere_style = $attribs; 3844 unset($critere_style['points']); 3845 $path_style = $this->svgDefineStyle($critere_style); 3846 3847 break; 3848 3849 // mPDF 5.7.4 Embedded image 3850 case 'image': 3851 3852 if (isset($attribs['xlink:href']) && $attribs['xlink:href']) { 3853 $this->svgImage($attribs); 3854 } 3855 3856 break; 3857 3858 case 'a': 3859 3860 if (isset($attribs['xlink:href'])) { 3861 unset($attribs['xlink:href']); // this should be a hyperlink 3862 // not handled like a xlink:href in other elements 3863 } 3864 3865 // fallthtough - then continue like a <g> 3866 3867 case 'g': 3868 3869 $array_style = $this->svgDefineStyle($attribs); 3870 3871 if (!empty($array_style['transformations'])) { 3872 // If in the middle of <text> element, add to textoutput, else WriteString 3873 if ($this->intext) { 3874 $this->textoutput .= ' q ' . $array_style['transformations']; 3875 } else { // mPDF 5.7.4 3876 $this->svgWriteString(' q ' . $array_style['transformations']); 3877 } 3878 } 3879 3880 array_push($this->svg_style, $array_style); 3881 3882 $this->svgDefineTxtStyle($attribs); 3883 3884 break; 3885 3886 case 'text': 3887 3888 $this->textlength = 0; // mPDF 5.7.4 3889 $this->texttotallength = 0; // mPDF 5.7.4 3890 $this->textoutput = ''; // mPDF 5.7.4 3891 $this->textanchor = 'start'; // mPDF 5.7.4 3892 $this->textXorigin = 0; // mPDF 5.7.4 3893 $this->textYorigin = 0; // mPDF 5.7.4 3894 3895 $this->intext = true; // mPDF 5.7.4 3896 3897 $styl = ''; 3898 3899 if ($this->mpdf->svgClasses && isset($attribs['class']) && $attribs['class']) { 3900 $classes = preg_split('/\s+/', trim($attribs['class'])); 3901 foreach ($classes as $class) { 3902 if (isset($this->cssManager->CSS['CLASS>>' . strtoupper($class)])) { 3903 $c = $this->cssManager->CSS['CLASS>>' . strtoupper($class)]; 3904 foreach ($c as $prop => $val) { 3905 $styl .= strtolower($prop) . ':' . $val . ';'; 3906 } 3907 } 3908 } 3909 } 3910 3911 if ($this->mpdf->svgAutoFont && isset($attribs['lang']) && $attribs['lang']) { 3912 if (!$this->mpdf->usingCoreFont) { 3913 if ($attribs['lang'] != $this->mpdf->default_lang) { 3914 list ($coreSuitable, $mpdf_unifont) = $this->languageToFont->getLanguageOptions($attribs['lang'], $this->mpdf->useAdobeCJK); 3915 if ($mpdf_unifont) { 3916 $styl .= 'font-family:' . $mpdf_unifont . ';'; 3917 } 3918 } 3919 } 3920 } 3921 3922 if ($styl) { 3923 if (isset($attribs['style'])) { 3924 $attribs['style'] = $styl . $attribs['style']; 3925 } else { 3926 $attribs['style'] = $styl; 3927 } 3928 } 3929 3930 $array_style = $this->svgDefineStyle($attribs); 3931 if (!empty($array_style['transformations'])) { 3932 $this->textoutput .= ' q ' . $array_style['transformations']; // mPDF 5.7.4 3933 } 3934 array_push($this->svg_style, $array_style); 3935 3936 $this->txt_data = []; 3937 $x = isset($attribs['x']) ? $this->ConvertSVGSizePixels($attribs['x'], 'x') : 0; // mPDF 5.7.4 3938 $y = isset($attribs['y']) ? $this->ConvertSVGSizePixels($attribs['y'], 'y') : 0; // mPDF 5.7.4 3939 $x += isset($attribs['dx']) ? $this->ConvertSVGSizePixels($attribs['dx'], 'x') : 0; // mPDF 5.7.4 3940 $y += isset($attribs['dy']) ? $this->ConvertSVGSizePixels($attribs['dy'], 'y') : 0; // mPDF 5.7.4 3941 3942 $this->txt_data[0] = $x; // mPDF 5.7.4 3943 $this->txt_data[1] = $y; // mPDF 5.7.4 3944 $critere_style = $attribs; 3945 unset($critere_style['x'], $critere_style['y']); 3946 $this->svgDefineTxtStyle($critere_style); 3947 3948 $this->textanchor = $this->txt_style[count($this->txt_style) - 1]['text-anchor']; // mPDF 5.7.4 3949 $this->textXorigin = $this->txt_data[0]; // mPDF 5.7.4 3950 $this->textYorigin = $this->txt_data[1]; // mPDF 5.7.4 3951 $this->textjuststarted = true; // mPDF 5.7.4 3952 3953 break; 3954 3955 // mPDF 5.7.4 3956 case 'tspan': 3957 3958 // OUTPUT CHUNK(s) UP To NOW (svgText updates $this->textlength) 3959 $p_cmd = $this->svgText(); 3960 $this->textoutput .= $p_cmd; 3961 $tmp = count($this->svg_style) - 1; 3962 $current_style = $this->svg_style[$tmp]; 3963 3964 $styl = ''; 3965 if ($this->mpdf->svgClasses && isset($attribs['class']) && $attribs['class']) { 3966 $classes = preg_split('/\s+/', trim($attribs['class'])); 3967 foreach ($classes as $class) { 3968 if (isset($this->cssManager->CSS['CLASS>>' . strtoupper($class)])) { 3969 $c = $this->cssManager->CSS['CLASS>>' . strtoupper($class)]; 3970 foreach ($c as $prop => $val) { 3971 $styl .= strtolower($prop) . ':' . $val . ';'; 3972 } 3973 } 3974 } 3975 } 3976 3977 if ($this->mpdf->svgAutoFont && isset($attribs['lang']) && $attribs['lang']) { 3978 if (!$this->mpdf->usingCoreFont) { 3979 if ($attribs['lang'] != $this->mpdf->default_lang) { 3980 list ($coreSuitable, $mpdf_unifont) = $this->languageToFont->getLanguageOptions($attribs['lang'], $this->mpdf->useAdobeCJK); 3981 if ($mpdf_unifont) { 3982 $styl .= 'font-family:' . $mpdf_unifont . ';'; 3983 } 3984 } 3985 } 3986 } 3987 3988 if ($styl) { 3989 if (isset($attribs['style'])) { 3990 $attribs['style'] = $styl . $attribs['style']; 3991 } else { 3992 $attribs['style'] = $styl; 3993 } 3994 } 3995 3996 $array_style = $this->svgDefineStyle($attribs); 3997 3998 $this->txt_data = []; 3999 4000 4001 // If absolute position adjustment (x or y), creates new block of text for text-alignment 4002 if (isset($attribs['x']) || isset($attribs['y'])) { 4003 // If text-anchor middle|end, adjust 4004 if ($this->textanchor == 'end') { 4005 $tx = -$this->texttotallength; 4006 } elseif ($this->textanchor == 'middle') { 4007 $tx = -$this->texttotallength / 2; 4008 } else { 4009 $tx = 0; 4010 } 4011 while (preg_match('/mPDF-AXS\((.*?)\)/', $this->textoutput, $m)) { 4012 if ($tx) { 4013 $txk = $m[1] + ($tx * $this->kp); 4014 $this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', sprintf('%.4F', $txk), $this->textoutput, 1); 4015 } else { 4016 $this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', '\\1', $this->textoutput, 1); 4017 } 4018 } 4019 4020 $this->svgWriteString($this->textoutput); 4021 4022 $this->textXorigin += $this->textlength; 4023 $currentX = $this->textXorigin; 4024 $currentY = $this->textYorigin; 4025 $this->textlength = 0; 4026 $this->texttotallength = 0; 4027 $this->textoutput = ''; 4028 4029 $x = isset($attribs['x']) ? $this->ConvertSVGSizePixels($attribs['x'], 'x') : $currentX; 4030 $y = isset($attribs['y']) ? $this->ConvertSVGSizePixels($attribs['y'], 'y') : $currentY; 4031 4032 $this->txt_data[0] = $x; 4033 $this->txt_data[1] = $y; 4034 $critere_style = $attribs; 4035 unset($critere_style['x'], $critere_style['y']); 4036 $this->svgDefineTxtStyle($critere_style); 4037 4038 $this->textanchor = $this->txt_style[count($this->txt_style) - 1]['text-anchor']; 4039 $this->textXorigin = $x; 4040 $this->textYorigin = $y; 4041 } else { 4042 $this->textXorigin += $this->textlength; 4043 $currentX = $this->textXorigin; 4044 $currentY = $this->textYorigin; 4045 4046 $currentX += isset($attribs['dx']) ? $this->ConvertSVGSizePixels($attribs['dx'], 'x') : 0; 4047 $currentY += isset($attribs['dy']) ? $this->ConvertSVGSizePixels($attribs['dy'], 'y') : 0; 4048 4049 $this->txt_data[0] = $currentX; 4050 $this->txt_data[1] = $currentY; 4051 $critere_style = $attribs; 4052 unset($critere_style['x'], $critere_style['y']); 4053 $this->svgDefineTxtStyle($critere_style); 4054 $this->textXorigin = $currentX; 4055 $this->textYorigin = $currentY; 4056 } 4057 4058 if (!empty($array_style['transformations'])) { 4059 $this->textoutput .= ' q ' . $array_style['transformations']; 4060 } 4061 array_push($this->svg_style, $array_style); 4062 4063 break; 4064 } 4065 4066 // insertion des path et du style dans le flux de donné general. 4067 if (isset($path_cmd) && $path_cmd) { 4068 // mPDF 5.0 4069 list($prestyle, $poststyle) = $this->svgStyle($path_style, $attribs, strtolower($name)); 4070 if (isset($path_style['transformations']) && $path_style['transformations']) { // transformation on an element 4071 $this->svgWriteString(" q " . $path_style['transformations'] . $prestyle . $path_cmd . $poststyle . " Q\n"); 4072 } else { 4073 $this->svgWriteString(" q " . $prestyle . $path_cmd . $poststyle . " Q\n"); // mPDF 5.7.4 4074 } 4075 } 4076 } 4077 4078 function characterData($parser, $data) 4079 { 4080 if ($this->inDefs) { 4081 return; 4082 } // mPDF 5.7.2 4083 4084 if (isset($this->txt_data[2])) { 4085 $this->txt_data[2] .= $data; 4086 } else { 4087 $this->txt_data[2] = $data; 4088 $this->txt_data[0] = $this->textXorigin; 4089 $this->txt_data[1] = $this->textYorigin; 4090 } 4091 } 4092 4093 function xml_svg2pdf_end($parser, $name) 4094 { 4095 // mPDF 5.7.2 4096 // Don't output stuff inside <defs> 4097 if ($name == 'defs') { 4098 $this->inDefs = false; 4099 return; 4100 } 4101 4102 if ($this->inDefs) { 4103 return; 4104 } 4105 4106 switch ($name) { 4107 case "g": 4108 case "a": 4109 if ($this->intext) { 4110 $p_cmd = $this->svgText(); 4111 $this->textoutput .= $p_cmd; 4112 } 4113 4114 $tmp = count($this->svg_style) - 1; 4115 $current_style = $this->svg_style[$tmp]; 4116 if (!empty($current_style['transformations'])) { 4117 // If in the middle of <text> element, add to textoutput, else WriteString 4118 if ($this->intext) { 4119 $this->textoutput .= " Q\n"; 4120 } // mPDF 5.7.4 4121 else { 4122 $this->svgWriteString(" Q\n"); 4123 } 4124 } 4125 4126 array_pop($this->svg_style); 4127 array_pop($this->txt_style); 4128 4129 if ($this->intext) { 4130 $this->textXorigin += $this->textlength; 4131 $this->textlength = 0; 4132 } 4133 4134 break; 4135 4136 case 'font': 4137 $last_svg_fontdefw = ''; 4138 break; 4139 4140 case 'font-face': 4141 $last_svg_fontid = ''; 4142 $last_svg_fontstyle = ''; 4143 break; 4144 4145 case 'radialgradient': 4146 case 'lineargradient': 4147 $last_gradid = ''; 4148 break; 4149 4150 case 'text': 4151 if (!empty($this->txt_data[2])) { 4152 $this->txt_data[2] = rtrim($this->txt_data[2]); // mPDF 5.7.4 4153 } 4154 4155 $path_cmd = $this->svgText(); 4156 $this->textoutput .= $path_cmd; // mPDF 5.7.4 4157 $tmp = count($this->svg_style) - 1; 4158 $current_style = $this->svg_style[$tmp]; 4159 4160 if (!empty($current_style['transformations'])) { 4161 $this->textoutput .= " Q\n"; // mPDF 5.7.4 4162 } 4163 array_pop($this->svg_style); 4164 array_pop($this->txt_style); // mPDF 5.7.4 4165 4166 // mPDF 5.7.4 4167 // If text-anchor middle|end, adjust 4168 if ($this->textanchor == 'end') { 4169 $tx = -$this->texttotallength; 4170 } elseif ($this->textanchor == 'middle') { 4171 $tx = -$this->texttotallength / 2; 4172 } else { 4173 $tx = 0; 4174 } 4175 while (preg_match('/mPDF-AXS\((.*?)\)/', $this->textoutput, $m)) { 4176 if ($tx) { 4177 $txk = $m[1] + ($tx * $this->kp); 4178 $this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', sprintf('%.4F', $txk), $this->textoutput, 1); 4179 } else { 4180 $this->textoutput = preg_replace('/mPDF-AXS\((.*?)\)/', '\\1', $this->textoutput, 1); 4181 } 4182 } 4183 4184 $this->svgWriteString($this->textoutput); 4185 $this->textlength = 0; 4186 $this->texttotallength = 0; 4187 $this->textoutput = ''; 4188 $this->intext = false; // mPDF 5.7.4 4189 4190 break; 4191 // mPDF 5.7.4 4192 case "tspan": 4193 $p_cmd = $this->svgText(); 4194 $this->textoutput .= $p_cmd; 4195 $tmp = count($this->svg_style) - 1; 4196 $current_style = $this->svg_style[$tmp]; 4197 if (!empty($current_style['transformations'])) { 4198 $this->textoutput .= " Q\n"; 4199 } 4200 array_pop($this->svg_style); 4201 array_pop($this->txt_style); 4202 4203 $this->textXorigin += $this->textlength; 4204 $this->textlength = 0; 4205 4206 break; 4207 } 4208 } 4209 4210 private function computeBezierBoundingBox($start, $c) 4211 { 4212 $P0 = [$start[0], $start[1]]; 4213 $P1 = [$c[0], $c[1]]; 4214 $P2 = [$c[2], $c[3]]; 4215 $P3 = [$c[4], $c[5]]; 4216 $bounds = []; 4217 $bounds[0][] = $P0[0]; 4218 $bounds[1][] = $P0[1]; 4219 $bounds[0][] = $P3[0]; 4220 $bounds[1][] = $P3[1]; 4221 4222 for ($i = 0; $i <= 1; $i++) { 4223 $b = 6 * $P0[$i] - 12 * $P1[$i] + 6 * $P2[$i]; 4224 $a = -3 * $P0[$i] + 9 * $P1[$i] - 9 * $P2[$i] + 3 * $P3[$i]; 4225 $c = 3 * $P1[$i] - 3 * $P0[$i]; 4226 4227 if ($a == 0) { 4228 4229 if ($b == 0) { 4230 continue; 4231 } 4232 4233 $t = -$c / $b; 4234 4235 if ($t > 0 && $t < 1) { 4236 $bounds[$i][] = (pow((1 - $t), 3) * $P0[$i] + 3 * pow((1 - $t), 2) * $t * $P1[$i] + 3 * (1 - $t) * pow($t, 2) * $P2[$i] + pow($t, 3) * $P3[$i]); 4237 } 4238 4239 continue; 4240 } 4241 4242 $b2ac = pow($b, 2) - 4 * $c * $a; 4243 4244 if ($b2ac < 0) { 4245 continue; 4246 } 4247 4248 $t1 = (-$b + sqrt($b2ac)) / (2 * $a); 4249 4250 if ($t1 > 0 && $t1 < 1) { 4251 $bounds[$i][] = (pow((1 - $t1), 3) * $P0[$i] + 3 * pow((1 - $t1), 2) * $t1 * $P1[$i] + 3 * (1 - $t1) * pow($t1, 2) * $P2[$i] + pow($t1, 3) * $P3[$i]); 4252 } 4253 4254 $t2 = (-$b - sqrt($b2ac)) / (2 * $a); 4255 4256 if ($t2 > 0 && $t2 < 1) { 4257 $bounds[$i][] = (pow((1 - $t2), 3) * $P0[$i] + 3 * pow((1 - $t2), 2) * $t2 * $P1[$i] + 3 * (1 - $t2) * pow($t2, 2) * $P2[$i] + pow($t2, 3) * $P3[$i]); 4258 } 4259 } 4260 4261 $x = min($bounds[0]); 4262 $x2 = max($bounds[0]); 4263 $y = min($bounds[1]); 4264 $y2 = max($bounds[1]); 4265 4266 return [$x, $y, $x2, $y2]; 4267 } 4268 4269 private function testIntersectCircle($cx, $cy, $cr) 4270 { 4271 // Tests whether a circle fully encloses a rectangle 0,0,1,1 4272 // to see if any further radial gradients need adding (SVG) 4273 // If centre of circle is inside 0,0,1,1 square 4274 if ($cx >= 0 && $cx <= 1 && $cy >= 0 && $cy <= 1) { 4275 $maxd = 1.5; 4276 } else { // distance to four corners 4277 $d1 = sqrt(pow(($cy - 0), 2) + pow(($cx - 0), 2)); 4278 $d2 = sqrt(pow(($cy - 1), 2) + pow(($cx - 0), 2)); 4279 $d3 = sqrt(pow(($cy - 0), 2) + pow(($cx - 1), 2)); 4280 $d4 = sqrt(pow(($cy - 1), 2) + pow(($cx - 1), 2)); 4281 $maxd = max($d1, $d2, $d3, $d4); 4282 } 4283 4284 return $cr < $maxd; 4285 } 4286 4287 private function testIntersect($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4) 4288 { 4289 // Tests whether line (x1, y1) and (x2, y2) [a gradient axis (perpendicular)] 4290 // intersects with a specific line segment (x3, y3) and (x4, y4) 4291 $a1 = $y2 - $y1; 4292 $b1 = $x1 - $x2; 4293 $c1 = $a1 * $x1 + $b1 * $y1; 4294 $a2 = $y4 - $y3; 4295 $b2 = $x3 - $x4; 4296 $c2 = $a2 * $x3 + $b2 * $y3; 4297 $det = $a1 * $b2 - $a2 * $b1; 4298 4299 if ($det == 0) { //Lines are parallel 4300 return false; 4301 } else { 4302 $x = ($b2 * $c1 - $b1 * $c2) / $det; 4303 $y = ($a1 * $c2 - $a2 * $c1) / $det; 4304 if ($x >= $x3 && $x <= $x4 && $y >= $y3 && $y <= $y4) { 4305 return true; 4306 } 4307 } 4308 4309 return false; 4310 } 4311 4312} 4313