1<?php 2/** 3 * Embed an image gallery 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8class syntax_plugin_panoview extends DokuWiki_Syntax_Plugin { 9 10 /** 11 * What kind of syntax are we? 12 */ 13 function getType() { 14 return 'substition'; 15 } 16 17 /** 18 * What about paragraphs? 19 */ 20 function getPType() { 21 return 'block'; 22 } 23 24 /** 25 * Where to sort in? 26 */ 27 function getSort() { 28 return 301; 29 } 30 31 /** 32 * Connect pattern to lexer 33 */ 34 function connectTo($mode) { 35 $this->Lexer->addSpecialPattern('\{\{panoview>[^}]*\}\}', $mode, 'plugin_panoview'); 36 } 37 38 /** 39 * Handle the match 40 */ 41 function handle($match, $state, $pos, Doku_Handler $handler) { 42 global $ID; 43 44 $data = array( 45 'width' => 500, 46 'height' => 250, 47 'align' => 0, 48 'initialZoom' => 1, 49 'tileBaseUri' => DOKU_BASE.'lib/plugins/panoview/tiles.php', 50 'tileSize' => 256, 51 'maxZoom' => 10, 52 'blankTile' => DOKU_BASE.'lib/plugins/panoview/gfx/blank.gif', 53 'loadingTile' => DOKU_BASE.'lib/plugins/panoview/gfx/progress.gif', 54 ); 55 56 $match = substr($match, 11, -2); //strip markup from start and end 57 58 // alignment 59 $data['align'] = 0; 60 if(substr($match, 0, 1) == ' ') $data['align'] += 1; 61 if(substr($match, -1, 1) == ' ') $data['align'] += 2; 62 63 // extract params 64 list($img, $params) = explode('?', $match, 2); 65 $img = trim($img); 66 67 // resolving relatives 68 $data['image'] = resolve_id(getNS($ID), $img); 69 70 $file = mediaFN($data['image']); 71 list($data['imageWidth'], $data['imageHeight']) = @getimagesize($file); 72 73 // calculate maximum zoom 74 $data['maxZoom'] = ceil(sqrt(max($data['imageWidth'], $data['imageHeight']) / $data['tileSize'])); 75 76 // size 77 if(preg_match('/\b(\d+)[xX](\d+)\b/', $params, $match)) { 78 $data['width'] = $match[1]; 79 $data['height'] = $match[2]; 80 } 81 82 // initial zoom 83 if(preg_match('/\b[zZ](\d+)\b/', $params, $match)) { 84 $data['initialZoom'] = $match[1]; 85 } 86 if($data['initialZoom'] < 0) $data['initialZoom'] = 0; 87 if($data['initialZoom'] > $data['maxZoom']) $data['initialZoom'] = $data['maxZoom']; 88 89 return $data; 90 } 91 92 /** 93 * Create output 94 */ 95 function render($mode, Doku_Renderer $R, $data) { 96 if($mode != 'xhtml') return false; 97 global $ID; 98 99 $img = '<a href="'.ml($data['image'], array('id'=> $ID), false).'"><img src="'. 100 ml($data['image'], array('w'=> $data['width'], 'h'=> $data['height'])).'" width="'. 101 $data['width'].'" height="'.$data['height'].'" alt="" /></a>'; 102 103 if($data['align'] == 1) { 104 $align = 'medialeft'; 105 } elseif($data['align'] == 2) { 106 $align = 'mediaright'; 107 } else { 108 $align = 'mediacenter'; 109 } 110 111 $R->doc .= ' 112 <div class="panoview_plugin '.$align.'" style="width: '.$data['width'].'px; height: '.$data['height'].'px;"> 113 <div class="well"><!-- --></div> 114 <div class="surface">'.$img.'</div> 115 <p class="controls" style="display: none"> 116 <span class="zoomIn" title="Zoom In">+</span> 117 <span class="zoomOut" title="Zoom Out">-</span> 118 <span class="maximize"><img src="'.DOKU_BASE.'lib/plugins/panoview/gfx/window.gif" style="position: absolute; bottom: 4px; right: 5px;" title="Maximize"></span> 119 </p> 120 <div class="options" style="display:none">'.hsc(json_encode($data)).'</div> 121 </div> 122 '; 123 124 return true; 125 } 126 127 // ----------- Tile Generator below --------------- 128 129 /** 130 * Create a tile using libGD 131 */ 132 function tile_gd($d) { 133 global $conf; 134 135 $img = null; 136 if(preg_match('/\.jpe?g$/', $d['file'])) { 137 $img = @imagecreatefromjpeg($d['file']); 138 } elseif(preg_match('/\.png$/', $d['file'])) { 139 $img = @imagecreatefrompng($d['file']); 140 } elseif(preg_match('/\.gif$/', $d['file'])) { 141 $img = @imagecreatefromgif($d['file']); 142 } 143 if(!$img) $this->gfx_error('generic'); 144 145 $crop = $this->image_crop($img, $d['width'], $d['height'], $d['tlx'], $d['tly'], $d['brx'], $d['bry']); 146 imagedestroy($img); 147 148 $scale = $this->image_scale($crop, abs($d['brx'] - $d['tlx']), abs($d['bry'] - $d['tly']), $d['ts'], $d['ts']); 149 imagedestroy($crop); 150 151 imagejpeg($scale, $d['cache'], $conf['jpg_quality']); 152 imagedestroy($scale); 153 154 if($conf['fperm']) chmod($d['cache'], $conf['fperm']); 155 } 156 157 /** 158 * Create a tile using Image Magick 159 */ 160 function tile_im($d) { 161 global $conf; 162 163 $cmd = $this->getConf('nice'); 164 $cmd .= ' '.$conf['im_convert']; 165 $cmd .= ' '.escapeshellarg($d['file']); 166 $cmd .= ' -crop \''.abs($d['brx'] - $d['tlx']).'x'.abs($d['bry'] - $d['tly']).'!+'.$d['tlx'].'+'.$d['tly'].'\''; 167 $cmd .= ' -background black'; 168 $cmd .= ' -extent \''.abs($d['brx'] - $d['tlx']).'x'.abs($d['bry'] - $d['tly']).'!\''; 169 $cmd .= ' -resize \''.$d['ts'].'x'.$d['ts'].'!\''; 170 171 $cmd .= ' -quality '.$conf['jpg_quality']; 172 $cmd .= ' '.escapeshellarg($d['cache']); 173 174 # dbg($cmd); exit; 175 176 @exec($cmd, $out, $retval); 177 if($retval == 0) return true; 178 $this->gfx_error('generic'); 179 } 180 181 /** 182 * Scale an image with libGD 183 */ 184 function image_scale($image, $x, $y, $w, $h) { 185 $scale = imagecreatetruecolor($w, $h); 186 imagecopyresampled($scale, $image, 0, 0, 0, 0, $w, $h, $x, $y); 187 return $scale; 188 } 189 190 /** 191 * Crop an image with libGD 192 */ 193 function image_crop($image, $x, $y, $left, $upper, $right, $lower) { 194 $w = abs($right - $left); 195 $h = abs($lower - $upper); 196 $crop = imagecreatetruecolor($w, $h); 197 imagecopy($crop, $image, 0, 0, $left, $upper, $w, $h); 198 return $crop; 199 } 200 201 /** 202 * Send a graphical error message and stop script 203 */ 204 function gfx_error($type) { 205 $file = dirname(__FILE__).'/gfx/'.$type.'.gif'; 206 $time = filemtime($file); 207 header('Content-type: image/gif'); 208 209 http_conditionalRequest($time); 210 http_sendfile($file); 211 readfile($file); 212 exit; 213 } 214 215 /** 216 * Acquire a lock for the tile generator 217 */ 218 function tile_lock($d) { 219 global $conf; 220 221 $lockDir = $conf['lockdir'].'/'.md5($d['id']).'.panoview'; 222 @ignore_user_abort(1); 223 224 $timeStart = time(); 225 do { 226 //waited longer than 25 seconds? -> stale lock? 227 if((time() - $timeStart) > 25) { 228 if(time() - @filemtime($lockDir) > 30) $this->tile_unlock($d); 229 send_redirect(DOKU_URL.'lib/plugins/panoview/tiles.php?tile='.$d['zoom'].'-'.$d['col'].'-'.$d['row'].'&image='.rawurlencode($d['id'])); 230 exit; 231 } 232 $locked = @mkdir($lockDir, $conf['dmode']); 233 if($locked) { 234 if(!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']); 235 break; 236 } 237 usleep(rand(500, 3000)); 238 } while($locked === false); 239 } 240 241 /** 242 * Unlock the tile generator 243 */ 244 function tile_unlock($d) { 245 global $conf; 246 247 $lockDir = $conf['lockdir'].'/'.md5($d['id']).'.panoview'; 248 @rmdir($lockDir); 249 @ignore_user_abort(0); 250 } 251} 252