1<?php 2/** 3 * DokuWiki Plugin svgEmbed (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Bowers <restlessmind@gmail.com> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14 15class syntax_plugin_svgembed extends DokuWiki_Syntax_Plugin 16{ 17 18 /** 19 * Get the new parameters added to the syntax. 20 * 21 * @param string $match The text that matched the lexer pattern that we are inspecting 22 */ 23 private function Get_New_Parameters($match, &$p) { 24 // Strip the opening and closing markup 25 $link = preg_replace(array('/^\{\{/', '/\}\}$/u'), '', $match); 26 27 // Split title from URL 28 $link = explode('|', $link, 2); 29 30 //remove aligning spaces 31 $link[0] = trim($link[0]); 32 33 //split into src and parameters (using the very last questionmark) 34 $pos = strrpos($link[0], '?'); 35 36 if ($pos !== false) { 37 $param = substr($link[0], $pos + 1); 38 39 // Get units 40 $p['inResponsiveUnits'] = (preg_match('/units:(\%|vw)/i', $param, $local_match) > 0); 41 $p['responsiveUnits'] = ($p['inResponsiveUnits'] && count($local_match) > 1) ? $local_match[1] : NULL; 42 43 // Get declared CSS classes 44 unset($local_match); 45 $p['hasCssClasses'] = (preg_match_all('/class:(-?[_a-z]+[_a-z0-9-]*)/i', $param, $local_match) > 0); 46 $p['cssClasses'] = ($p['hasCssClasses'] && isset($local_match[1]) && count($local_match[1])) ? $local_match[1] : NULL; 47 48 // Get printing 49 if (preg_match_all('/(^|&)(print|print:(on|true|yes|1|off|false|no|0))(&|$)/i', $param, $local_match)) { 50 $p['print'] = in_array(strtolower($local_match[2][0]), array('print', 'print:on', 'print:true', 'print:yes', 'print:1')); 51 } 52 else { 53 $p['print'] = ($this->getConf('default_print') == '1'); 54 } 55 56 // Re-parse width and height 57 $param = preg_replace('/class:(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)/i', '', $param); // Remove the classes since they can have numbers embedded 58 if(preg_match('/(\d+)(x(\d+))?/i', $param, $size)) { 59 $p['width'] = (!empty($size[1]) ? $size[1] : null); 60 $p['height'] = (!empty($size[3]) ? $size[3] : null); 61 } else { 62 $p['width'] = null; 63 $p['height'] = null; 64 } 65 } 66 } 67 68 69 /** 70 * Figure out the pixel adjustment if an absolute measurement unit is given. 71 * 72 * @param string $value Dimension to analyze for unit value (cm|mm|Q|in|pc|pt|px) 73 */ 74 private function Get_SVG_Unit_Adjustment($value) { 75 define('SVG_DPI', 96.0); 76 77 $matches = array(); 78 $adjustment = 1; 79 80 if (preg_match('/(cm|mm|Q|in|pc|pt|px)/', $value, $matches) && count($matches)) { 81 switch ($matches[1]) { 82 // Don't bother checking for "px", we already set adjustment to 1, but we still 83 // want to count it in the matches 84 case 'pt': 85 $adjustment = SVG_DPI / 72; 86 break; 87 case 'pc': 88 $adjustment = SVG_DPI / 6; 89 break; 90 case 'in': 91 $adjustment = SVG_DPI; 92 break; 93 case 'cm': 94 $adjustment = SVG_DPI / 2.54; 95 break; 96 case 'mm': 97 $adjustment = SVG_DPI / 25.4; 98 break; 99 case 'Q': 100 $adjustment = SVG_DPI / 101.6; 101 break; 102 } 103 } 104 105 return $adjustment; 106 } 107 108 109 /** 110 * @return string Syntax mode type 111 */ 112 public function getType() { 113 return 'container'; 114 } 115 116 /** 117 * @return string Paragraph type 118 */ 119 public function getPType() { 120 return 'block'; 121 } 122 123 /** 124 * @return int Sort order - Low numbers go before high numbers 125 */ 126 public function getSort() { 127 // Run it before the standard media functionality 128 return 315; 129 } 130 131 /** 132 * Connect lookup pattern to lexer. 133 * 134 * @param string $mode Parser mode 135 */ 136 public function connectTo($mode) { 137 // match everything the media component does, but short circuit into my code first 138 $this->Lexer->addSpecialPattern("\{\{(?:[^\}\>\<]|(?:\}[^\>\<\}]))+\}\}", $mode, 'plugin_svgembed'); 139 } 140 141 /** 142 * Handle matches of the media syntax, overridden by this plugin 143 * 144 * @param string $match The match of the syntax 145 * @param int $state The state of the handler 146 * @param int $pos The position in the document 147 * @param Doku_Handler $handler The handler 148 * 149 * @return array Data for the renderer 150 */ 151 public function handle($match, $state, $pos, Doku_Handler $handler) { 152 $p = Doku_Handler_Parse_Media($match); 153 $isSVG = preg_match('/\.svg$/i', trim($p['src'])); 154 if ($isSVG) 155 $this->Get_New_Parameters($match, $p); 156 157 if (!$isSVG || $p['type'] != 'internalmedia') { 158 // If it's external media or not an SVG, perform the regular processing... 159 $handler->media($match, $state, $pos); 160 return false; 161 } 162 else { 163 // ...otherwise, feed into my renderer 164 return ($p); 165 } 166 } 167 168 /** 169 * Render xhtml output or metadata 170 * 171 * @param string $mode Renderer mode (supported modes: xhtml, metadata) 172 * @param Doku_Renderer $renderer The renderer 173 * @param array $data The data from the handler() function 174 * 175 * @return bool If rendering was successful. 176 */ 177 public function render($mode, Doku_Renderer $renderer, $data) { 178 179 // If no data or we're not rendering XHTML, exit without handling 180 if (!$data) 181 return false; 182 183 if ($mode == 'xhtml') { 184 global $conf; 185 186 // Determine the maximum width allowed 187 if (isset($data['width'])) { 188 // Single width value specified? Render with this width, but determine the file height and scale accordingly 189 $svg_max = $data['width']; 190 } 191 else { 192 // If a value is set, use that, else load the default value 193 $svg_max = isset($conf['plugin']['svgembed']['max_svg_width']) ? 194 $conf['plugin']['svgembed']['max_svg_width'] : 195 $this->getConf('max_svg_width'); 196 } 197 198 // From here, it's basically a copy of the default renderer, but it inserts SVG with an embed tag rather than img tag. 199 $ret = ''; 200 $hasdimensions = (isset($data['width']) && isset($data['height'])); 201 202 // If both dimensions are not specified by the page then find them in the SVG file (if possible), and if not just pop out a default 203 if (!$hasdimensions) { 204 $svg_file = sprintf('%s%s', $conf['mediadir'], str_replace(':', '/', $data['src'])); 205 206 if (file_exists($svg_file) && ($svg_fp = fopen($svg_file, 'r'))) { 207 $svg_xml = simplexml_load_file($svg_file); 208 209 // Find the amount to adjust the pixels for layout if a unit is involved; use the 210 // largest adjustment if they are mixed 211 $svg_adjustment = max($this->Get_SVG_Unit_Adjustment($svg_xml->attributes()->width), 212 $this->Get_SVG_Unit_Adjustment($svg_xml->attributes()->height)); 213 214 $svg_width = round(floatval($svg_xml->attributes()->width) * $svg_adjustment); 215 $svg_height = round(floatval($svg_xml->attributes()->height) * $svg_adjustment); 216 217 if ($svg_width < 1 || $svg_height < 1) { 218 if (isset($svg_xml->attributes()->viewBox)) { 219 $svg_viewbox = preg_split('/[ ,]{1,}/', $svg_xml->attributes()->viewBox); 220 $svg_width = round(floatval($svg_viewbox[2])); 221 $svg_height = round(floatval($svg_viewbox[3])); 222 } 223 } 224 225 if ($svg_width < 1 || $svg_height < 1) { 226 $svg_width = isset($conf['plugin']['svgembed']['default_width']) ? 227 $conf['plugin']['svgembed']['default_width'] : 228 $this->getConf('default_width'); 229 $svg_height = isset($conf['plugin']['svgembed']['default_height']) ? 230 $conf['plugin']['svgembed']['default_height'] : 231 $this->getConf('default_height'); 232 } 233 234 unset($svg_viewbox, $svg_xml); 235 fclose($svg_fp); 236 } 237 238 // Make sure we're not exceeding the maximum width; if so, let's scale the SVG value to the maximum size 239 if ($svg_width > $svg_max) { 240 $svg_height = round($svg_height * $svg_max / $svg_width); 241 $svg_width = $svg_max; 242 } 243 244 $data['width'] = $svg_width; 245 $data['height'] = $svg_height; 246 } 247 else { 248 $svg_width = $data['width']; 249 $svg_height = $data['height']; 250 } 251 252 switch($data['align']) { 253 case 'center': 254 $styleextra = "margin:auto"; 255 break; 256 case 'left': 257 case 'right': 258 $styleextra = "float:" . urlencode($data['align']); 259 break; 260 default: 261 $styleextra = ''; 262 } 263 264 $svgembed_md5 = sprintf('svgembed_%s', md5(ml($data['src'], $ml_array))); 265 $ret .= '<span style="display:block'; 266 267 $spanunits = (isset($data['responsiveUnits'])) ? $data['responsiveUnits'] : 'px'; 268 269 if (isset($data['width'])) 270 $ret .= ";width:{$data['width']}{$spanunits}"; 271 272 if (isset($data['height'])) 273 $ret .= ";height:{$data['height']}{$spanunits}"; 274 275 if (strlen($styleextra)) 276 $ret .= ";{$styleextra}"; 277 278 $ret .= '">'; 279 280 281 $ml_array = array('cache' => $data['cache']); 282 if (!$data['inResponsiveUnits']) 283 $ml_array = array_merge($ml_array, array('w' => $data['width'], 'h' => $data['height'])); 284 285 $properties = '"' . ml($data['src'], $ml_array) . '" class="media' . $data['align'] . '"'; 286 287 if ($data['title']) { 288 $properties .= ' title="' . $data['title'] . '"'; 289 $properties .= ' alt="' . $data['title'] . '"'; 290 } else { 291 $properties .= ' alt=""'; 292 } 293 294 295 if (!(is_null($data['width']) || is_null($data['height']))) { 296 $properties .= ' style="width:100%"'; 297 } 298 299 300 // Add any extra specified classes to the objects 301 if ($data['hasCssClasses'] && count($data['cssClasses']) > 0) { 302 $additionalCssClasses = ''; 303 foreach ($data['cssClasses'] as $newCssClass) 304 $additionalCssClasses .= " " . $renderer->_xmlEntities($newCssClass); 305 $additionalCssClasses = trim($additionalCssClasses); 306 307 if (preg_match('/class="([^"]*)"/i', $properties, $pmatches)) { 308 $properties = str_replace("class=\"{$pmatches[1]}\"", "class=\"{$pmatches[1]} {$additionalCssClasses}\"", $properties); 309 } 310 311 unset($additionalCssClasses, $newCssClass); 312 } 313 314 if (is_a($renderer, 'renderer_plugin_dw2pdf')) { 315 $ret .= "<img id=\"" . $svgembed_md5 . "\" src={$properties} />"; 316 } 317 else { 318 $ret .= "<object id=\"" . $svgembed_md5 . "\" type=\"image/svg+xml\" data={$properties}><embed type=\"image/svg+xml\" src={$properties} /></object>"; 319 320 unset($properties); 321 322 if ($data['print']) { 323 $ret .= '<div class="svgprintbutton_table"><button type="submit" title="Print SVG" onClick="svgembed_printContent(\'' . 324 urlencode(ml($data['src'], $ml_array)) . '\'); return false" onMouseOver="svgembed_onMouseOver(\'' . 325 $svgembed_md5 . '\'); return false" ' . 'onMouseOut="svgembed_onMouseOut(\'' . $svgembed_md5 . '\'); return false"' . 326 '>Print SVG</button></div>'; 327 } 328 } 329 330 $ret .= '</span>'; 331 332 $ret .= '<br />'; 333 334 $renderer->doc .= $ret; 335 } 336 337 if ($mode == 'metadata') { 338 // Add metadata so the SVG is associated to the page 339 if ($data['type'] == 'internalmedia') { 340 global $ID; 341 342 $src = $data['src']; 343 list($src) = explode('#', $src, 2); 344 345 if (media_isexternal($src)) 346 return; 347 348 resolve_mediaid(getNS($ID), $src, $exists); 349 $renderer->meta['relation']['media'][$src] = $exists; 350 } 351 } 352 353 return true; 354 } 355} 356 357