1<?php 2 3/* 4 Phoronix Test Suite 5 URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/ 6 Copyright (C) 2011 - 2017, Phoronix Media 7 Copyright (C) 2011 - 2017, Michael Larabel 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 3 of the License, or 12 (at your option) any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program. If not, see <http://www.gnu.org/licenses/>. 21*/ 22 23class pts_svg_dom 24{ 25 protected $dom; 26 protected $svg; 27 protected $width; 28 protected $height; 29 30 public function __construct($width, $height) 31 { 32 $dom = new DOMImplementation(); 33 $dtd = $dom->createDocumentType('svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'); 34 $this->dom = $dom->createDocument(null, '', $dtd); 35 $this->dom->formatOutput = PTS_IS_CLIENT && PTS_IS_DEV_BUILD; 36 37 $pts_comment = $this->dom->createComment(pts_core::program_title(false) . ' [ http://www.phoronix-test-suite.com/ ]'); 38 $this->dom->appendChild($pts_comment); 39 40 $this->svg = $this->dom->createElementNS('http://www.w3.org/2000/svg', 'svg'); 41 $this->svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); 42 $this->svg->setAttribute('version', '1.1'); 43 $this->svg->setAttribute('font-family', 'sans-serif, droid-sans, helvetica, verdana, tahoma'); 44 $this->svg->setAttribute('viewbox', '0 0 ' . $width . ' ' . $height); 45 $this->svg->setAttribute('width', $width); 46 $this->svg->setAttribute('height', $height); 47 $this->svg->setAttribute('preserveAspectRatio', 'xMinYMin meet'); 48 $this->width = $width; 49 $this->height = $height; 50 51 $this->dom->appendChild($this->svg); 52 } 53 public function render_image($save_as = null, &$format = null) 54 { 55 // XXX: Alias for output. With PTS 3.8 this is just here for API compatibility with OpenBenchmarking.org. 56 $this->output($save_as, $format); 57 } 58 public function output($save_as = null, $output_format = 'SVG') 59 { 60 if(isset($_SERVER['HTTP_USER_AGENT']) || isset($_REQUEST['force_format'])) 61 { 62 static $browser_renderer = null; 63 64 if(isset($_REQUEST['force_format'])) 65 { 66 // Don't nest the force_format within the browser_renderer null check in case its overriden by OpenBenchmarking.org dynamically 67 $output_format = $_REQUEST['force_format']; 68 } 69 else if($browser_renderer == null) 70 { 71 $output_format = pts_render::renderer_compatibility_check($_SERVER['HTTP_USER_AGENT']); 72 } 73 else 74 { 75 $output_format = $browser_renderer; 76 } 77 } 78 $format = $output_format; 79 80 switch($output_format) 81 { 82 case 'JPG': 83 case 'JPEG': 84 $output = pts_svg_dom_gd::svg_dom_to_gd($this->dom, 'JPEG'); 85 $output_format = 'jpg'; 86 break; 87 case 'PNG': 88 $output = pts_svg_dom_gd::svg_dom_to_gd($this->dom, 'PNG'); 89 $output_format = 'png'; 90 break; 91 case 'HTML': 92 $html = new pts_svg_dom_html($this->dom); 93 $output = $html->get_html(); 94 $output_format = 'html'; 95 break; 96 case 'SVG': 97 default: 98 $output = $this->save_xml(); 99 $output_format = 'svg'; 100 break; 101 } 102 103 if($output == null) 104 { 105 return false; 106 } 107 else if($save_as) 108 { 109 return file_put_contents(str_replace('BILDE_EXTENSION', $output_format, $save_as), $output); 110 } 111 else 112 { 113 return $output; 114 } 115 } 116 public function save_xml() 117 { 118 return $this->dom->saveXML(); 119 } 120 public static function sanitize_hex($hex) 121 { 122 return $hex; // don't shorten it right now until the gd code can handle shortened hex codes 123 $hex = preg_replace('/(?<=^#)([a-f0-9])\\1([a-f0-9])\\2([a-f0-9])\\3\z/i', '\1\2\3', $hex); 124 125 return strtolower($hex); 126 } 127 public function draw_svg_line($start_x, $start_y, $end_x, $end_y, $color, $line_width = 1, $extra_elements = null) 128 { 129 // this function is equivalent to $this->svg_dom->add_element('line', array('x1' => , 'y1' => , 'x2' => , 'y2' => , 'stroke' => , 'stroke-width' => )); 130 $attributes = array('x1' => $start_x, 'y1' => $start_y, 'x2' => $end_x, 'y2' => $end_y, 'stroke' => $color, 'stroke-width' => $line_width); 131 132 if($extra_elements != null) 133 { 134 $attributes = array_merge($attributes, $extra_elements); 135 } 136 137 $this->add_element('line', $attributes); 138 } 139 public function draw_svg_arc($center_x, $center_y, $radius, $offset_percent, $percent, $attributes) 140 { 141 $deg = ($percent * 360); 142 $offset_deg = ($offset_percent * 360); 143 $arc = $percent > 0.5 ? 1 : 0; 144 145 $p1_x = round(cos(deg2rad($offset_deg)) * $radius) + $center_x; 146 $p1_y = round(sin(deg2rad($offset_deg)) * $radius) + $center_y; 147 $p2_x = round(cos(deg2rad($offset_deg + $deg)) * $radius) + $center_x; 148 $p2_y = round(sin(deg2rad($offset_deg + $deg)) * $radius) + $center_y; 149 150 $attributes['d'] = "M$center_x,$center_y L$p1_x,$p1_y A$radius,$radius 0 $arc,1 $p2_x,$p2_y Z"; 151 $this->add_element('path', $attributes); 152 } 153 public function draw_svg_circle($center_x, $center_y, $radius, $color, $extra_attributes = null) 154 { 155 $extra_attributes['cx'] = $center_x; 156 $extra_attributes['cy'] = $center_y; 157 $extra_attributes['r'] = $radius; 158 $extra_attributes['fill'] = $color; 159 $this->add_element('circle', $extra_attributes); 160 } 161 public function make_a($url) 162 { 163 $el = $this->dom->createElement('a'); 164 $el->setAttribute('xlink:href', $url); 165 $el->setAttribute('target', '_blank'); 166 return $this->svg->appendChild($el); 167 } 168 public function make_g($attributes = array(), $append_to = false) 169 { 170 $el = $this->dom->createElement('g'); 171 foreach($attributes as $name => $value) 172 { 173 $el->setAttribute($name, $value); 174 } 175 return $append_to ? $append_to->appendChild($el) : $this->svg->appendChild($el); 176 } 177 public function add_element($element_type, $attributes = array(), $append_to = false) 178 { 179 $el = $this->dom->createElement($element_type); 180 181 if(isset($attributes['xlink:href']) && $attributes['xlink:href'] != null && $element_type != 'a' && ($element_type != 'image' || (isset($attributes['http_link']) && $attributes['http_link'] != null))) 182 { 183 // image tag uses xlink:href as the image src, so check for 'http_link' instead to make a link out of it 184 $link_key = ($element_type == 'image' ? 'http_link' : 'xlink:href'); 185 $link = $this->dom->createElement('a'); 186 $link->setAttribute('xlink:href', $attributes[$link_key]); 187 $link->setAttribute('target', '_blank'); 188 $link->appendChild($el); 189 if($append_to) 190 { 191 $append_to->appendChild($link); 192 } 193 else 194 { 195 $this->svg->appendChild($link); 196 } 197 unset($attributes[$link_key]); 198 } 199 else 200 { 201 if($append_to) 202 { 203 $append_to->appendChild($el); 204 } 205 else 206 { 207 $this->svg->appendChild($el); 208 } 209 } 210 211 foreach($attributes as $name => $value) 212 { 213 $el->setAttribute($name, $value); 214 } 215 } 216 public function add_text_element($text_string, $attributes, $append_to = false) 217 { 218 $el = $this->dom->createElement('text'); 219 $text_node = $this->dom->createTextNode($text_string); 220 $el->appendChild($text_node); 221 222 if(isset($attributes['xlink:href']) && $attributes['xlink:href'] != null) 223 { 224 $link = $this->dom->createElement('a'); 225 $link->setAttribute('xlink:href', $attributes['xlink:href']); 226 $link->setAttribute('target', '_blank'); 227 $link->appendChild($el); 228 if($append_to) 229 { 230 $append_to->appendChild($link); 231 } 232 else 233 { 234 $this->svg->appendChild($link); 235 } 236 unset($attributes['xlink:href']); 237 } 238 else 239 { 240 if($append_to) 241 { 242 $append_to->appendChild($el); 243 } 244 else 245 { 246 $this->svg->appendChild($el); 247 } 248 } 249 250 foreach($attributes as $name => $value) 251 { 252 if($value == null && $value !== 0) 253 { 254 continue; 255 } 256 257 $el->setAttribute($name, $value); 258 } 259 } 260 public function add_textarea_element($text_string, $attributes, &$estimated_height = 0, &$append_to = false) 261 { 262 if(!isset($attributes['width'])) 263 { 264 $attributes['width'] = $this->width - $attributes['x']; 265 } 266 267 $queue_dimensions = self::estimate_text_dimensions($text_string, $attributes['font-size']); 268 if($queue_dimensions[0] < $attributes['width']) 269 { 270 // No wrapping is occuring, so stuff it in a more efficient text element instead 271 unset($attributes['width']); 272 $this->add_text_element($text_string, $attributes); 273 $estimated_height += ($attributes['font-size'] + 3); 274 return; 275 } 276 277 $el = $this->dom->createElement('text'); 278 $word_queue = null; 279 $line_count = 0; 280 $words = explode(' ', $text_string); 281 $word_count = count($words); 282 $last_word = null; 283 284 foreach($words as $i => $word) 285 { 286 $word_queue .= $word . ' '; 287 288 $queue_dimensions = self::estimate_text_dimensions($word_queue, ($attributes['font-size'] - 0.45)); 289 if($queue_dimensions[0] > $attributes['width'] || $i == ($word_count - 1)) 290 { 291 if($i != ($word_count - 1)) 292 { 293 $last_word_pos = strrpos($word_queue, ' ', -2); 294 $last_word = substr($word_queue, $last_word_pos); 295 $word_queue = substr($word_queue, 0, $last_word_pos); 296 } 297 298 $tspan = $this->dom->createElement('tspan'); 299 $tspan->setAttribute('x', $attributes['x']); 300 $tspan->setAttribute('y', $attributes['y']); 301 $tspan->setAttribute('dx', ($line_count == 0 ? 0 : 5)); 302 $tspan->setAttribute('dy', ($line_count * ($attributes['font-size'] + 1))); 303 $text_node = $this->dom->createTextNode($word_queue); 304 $tspan->appendChild($text_node); 305 $el->appendChild($tspan); 306 $word_queue = $last_word; 307 $line_count++; 308 } 309 } 310 $estimated_height += $line_count * ($attributes['font-size'] + 2); 311 unset($attributes['width']); 312 313 if(isset($attributes['xlink:href']) && $attributes['xlink:href'] != null) 314 { 315 $link = $this->dom->createElement('a'); 316 $link->setAttribute('xlink:href', $attributes['xlink:href']); 317 $link->setAttribute('target', '_blank'); 318 $link->appendChild($el); 319 if($append_to) 320 { 321 $append_to->appendChild($link); 322 } 323 else 324 { 325 $this->svg->appendChild($link); 326 } 327 unset($attributes['xlink:href']); 328 } 329 else 330 { 331 if($append_to) 332 { 333 $append_to->appendChild($el); 334 } 335 else 336 { 337 $this->svg->appendChild($el); 338 } 339 } 340 341 foreach($attributes as $name => $value) 342 { 343 $el->setAttribute($name, $value); 344 } 345 } 346 public function draw_rectangle_gradient($x1, $y1, $width, $height, $color, $next_color) 347 { 348 static $gradient_count = 1; 349 350 $gradient = $this->dom->createElement('linearGradient'); 351 $gradient->setAttribute('id', 'g_' . $gradient_count); 352 $gradient->setAttribute('x1', '0%'); 353 $gradient->setAttribute('y1', '0%'); 354 $gradient->setAttribute('x2', '100%'); 355 $gradient->setAttribute('y2', '0%'); 356 357 $stop = $this->dom->createElement('stop'); 358 $stop->setAttribute('offset', '0%'); 359 $stop->setAttribute('style', 'stop-color: ' . $color .'; stop-opacity: 1;'); 360 $gradient->appendChild($stop); 361 362 $stop = $this->dom->createElement('stop'); 363 $stop->setAttribute('offset', '100%'); 364 $stop->setAttribute('style', 'stop-color: ' . $next_color .'; stop-opacity: 1;'); 365 $gradient->appendChild($stop); 366 367 $defs = $this->dom->createElement('defs'); 368 $defs->appendChild($gradient); 369 $this->svg->appendChild($defs); 370 371 $rect = $this->dom->createElement('rect'); 372 $rect->setAttribute('x', $x1); 373 $rect->setAttribute('y', $y1); 374 $rect->setAttribute('width', $width); 375 $rect->setAttribute('height', $height); 376 //$rect->setAttribute('fill', $background_color); 377 $rect->setAttribute('style', 'fill:url(#g_' . $gradient_count . ')'); 378 $gradient_count++; 379 380 $this->svg->appendChild($rect); 381 } 382 public static function html_embed_code($file_name, $file_type = 'SVG', $attributes = null, $is_xsl = false) 383 { 384 $attributes = pts_arrays::to_array($attributes); 385 $file_name = str_replace('BILDE_EXTENSION', strtolower($file_type), $file_name); 386 387 switch($file_type) 388 { 389 case 'SVG': 390 $attributes['data'] = $file_name; 391 392 if($is_xsl) 393 { 394 $html = '<object type="image/svg+xml">'; 395 396 foreach($attributes as $option => $value) 397 { 398 $html .= '<xsl:attribute name="' . $option . '">' . $value . '</xsl:attribute>'; 399 } 400 $html .= '</object>'; 401 } 402 else 403 { 404 $html = '<object type="image/svg+xml"'; 405 406 foreach($attributes as $option => $value) 407 { 408 $html .= $option . '="' . $value . '" '; 409 } 410 $html .= '/>'; 411 } 412 break; 413 default: 414 $attributes['src'] = $file_name; 415 416 if($is_xsl) 417 { 418 $html = '<img>'; 419 420 foreach($attributes as $option => $value) 421 { 422 $html .= '<xsl:attribute name="' . $option . '">' . $value . '</xsl:attribute>'; 423 } 424 $html .= '</img>'; 425 } 426 else 427 { 428 $html = '<img '; 429 430 foreach($attributes as $option => $value) 431 { 432 $html .= $option . '="' . $value . '" '; 433 } 434 $html .= '/>'; 435 } 436 break; 437 } 438 439 return $html; 440 } 441 public static function estimate_text_dimensions($text_string, $font_size) 442 { 443 if($text_string == null) 444 { 445 return array(0, 0); 446 } 447 $box_height = ceil(0.76 * $font_size); 448 $box_width = ceil((0.76 * strlen($text_string) * $font_size) - ceil(strlen($text_string) * 1.05)); 449 450 // Width x Height 451 return array($box_width, $box_height); 452 } 453 public static function embed_png_image($png_img_file) 454 { 455 return 'data:image/png;base64,' . base64_encode(file_get_contents($png_img_file)); 456 } 457} 458 459?> 460