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