1<?php 2/** 3 * @package php-svg-lib 4 * @link http://github.com/PhenX/php-svg-lib 5 * @author Fabien M�nager <fabien.menager@gmail.com> 6 * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html 7 */ 8 9namespace Svg; 10 11use Svg\Tag\AbstractTag; 12 13class Style 14{ 15 const TYPE_COLOR = 1; 16 const TYPE_LENGTH = 2; 17 const TYPE_NAME = 3; 18 const TYPE_ANGLE = 4; 19 const TYPE_NUMBER = 5; 20 21 public $color; 22 public $opacity; 23 public $display; 24 25 public $fill; 26 public $fillOpacity; 27 public $fillRule; 28 29 public $stroke; 30 public $strokeOpacity; 31 public $strokeLinecap; 32 public $strokeLinejoin; 33 public $strokeMiterlimit; 34 public $strokeWidth; 35 public $strokeDasharray; 36 public $strokeDashoffset; 37 38 public $fontFamily = 'serif'; 39 public $fontSize = 12; 40 public $fontWeight = 'normal'; 41 public $fontStyle = 'normal'; 42 public $textAnchor = 'start'; 43 44 protected function getStyleMap() 45 { 46 return array( 47 'color' => array('color', self::TYPE_COLOR), 48 'opacity' => array('opacity', self::TYPE_NUMBER), 49 'display' => array('display', self::TYPE_NAME), 50 51 'fill' => array('fill', self::TYPE_COLOR), 52 'fill-opacity' => array('fillOpacity', self::TYPE_NUMBER), 53 'fill-rule' => array('fillRule', self::TYPE_NAME), 54 55 'stroke' => array('stroke', self::TYPE_COLOR), 56 'stroke-dasharray' => array('strokeDasharray', self::TYPE_NAME), 57 'stroke-dashoffset' => array('strokeDashoffset', self::TYPE_NUMBER), 58 'stroke-linecap' => array('strokeLinecap', self::TYPE_NAME), 59 'stroke-linejoin' => array('strokeLinejoin', self::TYPE_NAME), 60 'stroke-miterlimit' => array('strokeMiterlimit', self::TYPE_NUMBER), 61 'stroke-opacity' => array('strokeOpacity', self::TYPE_NUMBER), 62 'stroke-width' => array('strokeWidth', self::TYPE_NUMBER), 63 64 'font-family' => array('fontFamily', self::TYPE_NAME), 65 'font-size' => array('fontSize', self::TYPE_NUMBER), 66 'font-weight' => array('fontWeight', self::TYPE_NAME), 67 'font-style' => array('fontStyle', self::TYPE_NAME), 68 'text-anchor' => array('textAnchor', self::TYPE_NAME), 69 ); 70 } 71 72 /** 73 * @param $attributes 74 * 75 * @return Style 76 */ 77 public function fromAttributes($attributes) 78 { 79 $this->fillStyles($attributes); 80 81 if (isset($attributes["style"])) { 82 $styles = self::parseCssStyle($attributes["style"]); 83 $this->fillStyles($styles); 84 } 85 } 86 87 public function inherit(AbstractTag $tag) { 88 $group = $tag->getParentGroup(); 89 if ($group) { 90 $parent_style = $group->getStyle(); 91 92 foreach ($parent_style as $_key => $_value) { 93 if ($_value !== null) { 94 $this->$_key = $_value; 95 } 96 } 97 } 98 } 99 100 public function fromStyleSheets(AbstractTag $tag, $attributes) { 101 $class = isset($attributes["class"]) ? preg_split('/\s+/', trim($attributes["class"])) : null; 102 103 $stylesheets = $tag->getDocument()->getStyleSheets(); 104 105 $styles = array(); 106 107 foreach ($stylesheets as $_sc) { 108 109 /** @var \Sabberworm\CSS\RuleSet\DeclarationBlock $_decl */ 110 foreach ($_sc->getAllDeclarationBlocks() as $_decl) { 111 112 /** @var \Sabberworm\CSS\Property\Selector $_selector */ 113 foreach ($_decl->getSelectors() as $_selector) { 114 $_selector = $_selector->getSelector(); 115 116 // Match class name 117 if ($class !== null) { 118 foreach ($class as $_class) { 119 if ($_selector === ".$_class") { 120 /** @var \Sabberworm\CSS\Rule\Rule $_rule */ 121 foreach ($_decl->getRules() as $_rule) { 122 $styles[$_rule->getRule()] = $_rule->getValue() . ""; 123 } 124 125 break 2; 126 } 127 } 128 } 129 130 // Match tag name 131 if ($_selector === $tag->tagName) { 132 /** @var \Sabberworm\CSS\Rule\Rule $_rule */ 133 foreach ($_decl->getRules() as $_rule) { 134 $styles[$_rule->getRule()] = $_rule->getValue() . ""; 135 } 136 137 break; 138 } 139 } 140 } 141 } 142 143 $this->fillStyles($styles); 144 } 145 146 protected function fillStyles($styles) 147 { 148 foreach ($this->getStyleMap() as $from => $spec) { 149 if (isset($styles[$from])) { 150 list($to, $type) = $spec; 151 $value = null; 152 switch ($type) { 153 case self::TYPE_COLOR: 154 $value = self::parseColor($styles[$from]); 155 break; 156 157 case self::TYPE_NUMBER: 158 $value = ($styles[$from] === null) ? null : (float)$styles[$from]; 159 break; 160 161 default: 162 $value = $styles[$from]; 163 } 164 165 if ($value !== null) { 166 $this->$to = $value; 167 } 168 } 169 } 170 } 171 172 static function parseColor($color) 173 { 174 $color = strtolower(trim($color)); 175 176 $parts = preg_split('/[^,]\s+/', $color, 2); 177 178 if (count($parts) == 2) { 179 $color = $parts[1]; 180 } 181 else { 182 $color = $parts[0]; 183 } 184 185 if ($color === "none") { 186 return "none"; 187 } 188 189 // SVG color name 190 if (isset(self::$colorNames[$color])) { 191 return self::parseHexColor(self::$colorNames[$color]); 192 } 193 194 // Hex color 195 if ($color[0] === "#") { 196 return self::parseHexColor($color); 197 } 198 199 // RGB color 200 if (strpos($color, "rgb") !== false) { 201 return self::getTriplet($color); 202 } 203 204 // RGB color 205 if (strpos($color, "hsl") !== false) { 206 $triplet = self::getTriplet($color, true); 207 208 if ($triplet == null) { 209 return null; 210 } 211 212 list($h, $s, $l) = $triplet; 213 214 $r = $l; 215 $g = $l; 216 $b = $l; 217 $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s); 218 if ($v > 0) { 219 $m = $l + $l - $v; 220 $sv = ($v - $m) / $v; 221 $h *= 6.0; 222 $sextant = floor($h); 223 $fract = $h - $sextant; 224 $vsf = $v * $sv * $fract; 225 $mid1 = $m + $vsf; 226 $mid2 = $v - $vsf; 227 228 switch ($sextant) { 229 case 0: 230 $r = $v; 231 $g = $mid1; 232 $b = $m; 233 break; 234 case 1: 235 $r = $mid2; 236 $g = $v; 237 $b = $m; 238 break; 239 case 2: 240 $r = $m; 241 $g = $v; 242 $b = $mid1; 243 break; 244 case 3: 245 $r = $m; 246 $g = $mid2; 247 $b = $v; 248 break; 249 case 4: 250 $r = $mid1; 251 $g = $m; 252 $b = $v; 253 break; 254 case 5: 255 $r = $v; 256 $g = $m; 257 $b = $mid2; 258 break; 259 } 260 } 261 262 return array( 263 $r * 255.0, 264 $g * 255.0, 265 $b * 255.0, 266 ); 267 } 268 269 // Gradient 270 if (strpos($color, "url(#") !== false) { 271 $i = strpos($color, "("); 272 $j = strpos($color, ")"); 273 274 // Bad url format 275 if ($i === false || $j === false) { 276 return null; 277 } 278 279 return trim(substr($color, $i + 1, $j - $i - 1)); 280 } 281 282 return null; 283 } 284 285 static function getTriplet($color, $percent = false) { 286 $i = strpos($color, "("); 287 $j = strpos($color, ")"); 288 289 // Bad color value 290 if ($i === false || $j === false) { 291 return null; 292 } 293 294 $triplet = preg_split("/\\s*,\\s*/", trim(substr($color, $i + 1, $j - $i - 1))); 295 296 if (count($triplet) != 3) { 297 return null; 298 } 299 300 foreach (array_keys($triplet) as $c) { 301 $triplet[$c] = trim($triplet[$c]); 302 303 if ($percent) { 304 if ($triplet[$c][strlen($triplet[$c]) - 1] === "%") { 305 $triplet[$c] = $triplet[$c] / 100; 306 } 307 else { 308 $triplet[$c] = $triplet[$c] / 255; 309 } 310 } 311 else { 312 if ($triplet[$c][strlen($triplet[$c]) - 1] === "%") { 313 $triplet[$c] = round($triplet[$c] * 2.55); 314 } 315 } 316 } 317 318 return $triplet; 319 } 320 321 static function parseHexColor($hex) 322 { 323 $c = array(0, 0, 0); 324 325 // #FFFFFF 326 if (isset($hex[6])) { 327 $c[0] = hexdec(substr($hex, 1, 2)); 328 $c[1] = hexdec(substr($hex, 3, 2)); 329 $c[2] = hexdec(substr($hex, 5, 2)); 330 } else { 331 $c[0] = hexdec($hex[1] . $hex[1]); 332 $c[1] = hexdec($hex[2] . $hex[2]); 333 $c[2] = hexdec($hex[3] . $hex[3]); 334 } 335 336 return $c; 337 } 338 339 /** 340 * Simple CSS parser 341 * 342 * @param $style 343 * 344 * @return array 345 */ 346 static function parseCssStyle($style) 347 { 348 $matches = array(); 349 preg_match_all("/([a-z-]+)\\s*:\\s*([^;$]+)/si", $style, $matches, PREG_SET_ORDER); 350 351 $styles = array(); 352 foreach ($matches as $match) { 353 $styles[$match[1]] = $match[2]; 354 } 355 356 return $styles; 357 } 358 359 /** 360 * Convert a size to a float 361 * 362 * @param string $size SVG size 363 * @param float $dpi DPI 364 * @param float $referenceSize Reference size 365 * 366 * @return float|null 367 */ 368 static function convertSize($size, $referenceSize = 11.0, $dpi = 96.0) { 369 $size = trim(strtolower($size)); 370 371 if (is_numeric($size)) { 372 return $size; 373 } 374 375 if ($pos = strpos($size, "px")) { 376 return floatval(substr($size, 0, $pos)); 377 } 378 379 if ($pos = strpos($size, "pt")) { 380 return floatval(substr($size, 0, $pos)); 381 } 382 383 if ($pos = strpos($size, "cm")) { 384 return floatval(substr($size, 0, $pos)) * $dpi; 385 } 386 387 if ($pos = strpos($size, "%")) { 388 return $referenceSize * substr($size, 0, $pos) / 100; 389 } 390 391 if ($pos = strpos($size, "em")) { 392 return $referenceSize * substr($size, 0, $pos); 393 } 394 395 // TODO cm, mm, pc, in, etc 396 397 return null; 398 } 399 400 static $colorNames = array( 401 'antiquewhite' => '#FAEBD7', 402 'aqua' => '#00FFFF', 403 'aquamarine' => '#7FFFD4', 404 'beige' => '#F5F5DC', 405 'black' => '#000000', 406 'blue' => '#0000FF', 407 'brown' => '#A52A2A', 408 'cadetblue' => '#5F9EA0', 409 'chocolate' => '#D2691E', 410 'cornflowerblue' => '#6495ED', 411 'crimson' => '#DC143C', 412 'darkblue' => '#00008B', 413 'darkgoldenrod' => '#B8860B', 414 'darkgreen' => '#006400', 415 'darkmagenta' => '#8B008B', 416 'darkorange' => '#FF8C00', 417 'darkred' => '#8B0000', 418 'darkseagreen' => '#8FBC8F', 419 'darkslategray' => '#2F4F4F', 420 'darkviolet' => '#9400D3', 421 'deepskyblue' => '#00BFFF', 422 'dodgerblue' => '#1E90FF', 423 'firebrick' => '#B22222', 424 'forestgreen' => '#228B22', 425 'fuchsia' => '#FF00FF', 426 'gainsboro' => '#DCDCDC', 427 'gold' => '#FFD700', 428 'gray' => '#808080', 429 'green' => '#008000', 430 'greenyellow' => '#ADFF2F', 431 'hotpink' => '#FF69B4', 432 'indigo' => '#4B0082', 433 'khaki' => '#F0E68C', 434 'lavenderblush' => '#FFF0F5', 435 'lemonchiffon' => '#FFFACD', 436 'lightcoral' => '#F08080', 437 'lightgoldenrodyellow' => '#FAFAD2', 438 'lightgreen' => '#90EE90', 439 'lightsalmon' => '#FFA07A', 440 'lightskyblue' => '#87CEFA', 441 'lightslategray' => '#778899', 442 'lightyellow' => '#FFFFE0', 443 'lime' => '#00FF00', 444 'limegreen' => '#32CD32', 445 'magenta' => '#FF00FF', 446 'maroon' => '#800000', 447 'mediumaquamarine' => '#66CDAA', 448 'mediumorchid' => '#BA55D3', 449 'mediumseagreen' => '#3CB371', 450 'mediumspringgreen' => '#00FA9A', 451 'mediumvioletred' => '#C71585', 452 'midnightblue' => '#191970', 453 'mintcream' => '#F5FFFA', 454 'moccasin' => '#FFE4B5', 455 'navy' => '#000080', 456 'olive' => '#808000', 457 'orange' => '#FFA500', 458 'orchid' => '#DA70D6', 459 'palegreen' => '#98FB98', 460 'palevioletred' => '#D87093', 461 'peachpuff' => '#FFDAB9', 462 'pink' => '#FFC0CB', 463 'powderblue' => '#B0E0E6', 464 'purple' => '#800080', 465 'red' => '#FF0000', 466 'royalblue' => '#4169E1', 467 'salmon' => '#FA8072', 468 'seagreen' => '#2E8B57', 469 'sienna' => '#A0522D', 470 'silver' => '#C0C0C0', 471 'skyblue' => '#87CEEB', 472 'slategray' => '#708090', 473 'springgreen' => '#00FF7F', 474 'steelblue' => '#4682B4', 475 'tan' => '#D2B48C', 476 'teal' => '#008080', 477 'thistle' => '#D8BFD8', 478 'turquoise' => '#40E0D0', 479 'violetred' => '#D02090', 480 'white' => '#FFFFFF', 481 'yellow' => '#FFFF00', 482 'aliceblue' => '#f0f8ff', 483 'azure' => '#f0ffff', 484 'bisque' => '#ffe4c4', 485 'blanchedalmond' => '#ffebcd', 486 'blueviolet' => '#8a2be2', 487 'burlywood' => '#deb887', 488 'chartreuse' => '#7fff00', 489 'coral' => '#ff7f50', 490 'cornsilk' => '#fff8dc', 491 'cyan' => '#00ffff', 492 'darkcyan' => '#008b8b', 493 'darkgray' => '#a9a9a9', 494 'darkgrey' => '#a9a9a9', 495 'darkkhaki' => '#bdb76b', 496 'darkolivegreen' => '#556b2f', 497 'darkorchid' => '#9932cc', 498 'darksalmon' => '#e9967a', 499 'darkslateblue' => '#483d8b', 500 'darkslategrey' => '#2f4f4f', 501 'darkturquoise' => '#00ced1', 502 'deeppink' => '#ff1493', 503 'dimgray' => '#696969', 504 'dimgrey' => '#696969', 505 'floralwhite' => '#fffaf0', 506 'ghostwhite' => '#f8f8ff', 507 'goldenrod' => '#daa520', 508 'grey' => '#808080', 509 'honeydew' => '#f0fff0', 510 'indianred' => '#cd5c5c', 511 'ivory' => '#fffff0', 512 'lavender' => '#e6e6fa', 513 'lawngreen' => '#7cfc00', 514 'lightblue' => '#add8e6', 515 'lightcyan' => '#e0ffff', 516 'lightgray' => '#d3d3d3', 517 'lightgrey' => '#d3d3d3', 518 'lightpink' => '#ffb6c1', 519 'lightseagreen' => '#20b2aa', 520 'lightslategrey' => '#778899', 521 'lightsteelblue' => '#b0c4de', 522 'linen' => '#faf0e6', 523 'mediumblue' => '#0000cd', 524 'mediumpurple' => '#9370db', 525 'mediumslateblue' => '#7b68ee', 526 'mediumturquoise' => '#48d1cc', 527 'mistyrose' => '#ffe4e1', 528 'navajowhite' => '#ffdead', 529 'oldlace' => '#fdf5e6', 530 'olivedrab' => '#6b8e23', 531 'orangered' => '#ff4500', 532 'palegoldenrod' => '#eee8aa', 533 'paleturquoise' => '#afeeee', 534 'papayawhip' => '#ffefd5', 535 'peru' => '#cd853f', 536 'plum' => '#dda0dd', 537 'rosybrown' => '#bc8f8f', 538 'saddlebrown' => '#8b4513', 539 'sandybrown' => '#f4a460', 540 'seashell' => '#fff5ee', 541 'slateblue' => '#6a5acd', 542 'slategrey' => '#708090', 543 'snow' => '#fffafa', 544 'tomato' => '#ff6347', 545 'violet' => '#ee82ee', 546 'wheat' => '#f5deb3', 547 'whitesmoke' => '#f5f5f5', 548 'yellowgreen' => '#9acd32', 549 ); 550}