1<?php 2 3 4namespace ComboStrap; 5 6use ComboStrap\Web\Url; 7 8/** 9 * 10 * A Image Raster processing class that: 11 * * takes as input: 12 * * a {@link FetcherRaster::buildFromUrl() fetch URL}: 13 * * from an HTTP request 14 * * or {@link MediaMarkup::getFetchUrl() markup}) 15 * * or data via setter 16 * * outputs: 17 * * a {@link FetcherRaster::getFetchPath() raster image file} for: 18 * * an HTTP response 19 * * or further local processing 20 * * or a {@link FetcherRaster::getFetchUrl() fetch url} to use in a {@link RasterImageLink img html tag} 21 * 22 */ 23class FetcherRaster extends IFetcherLocalImage 24{ 25 26 use FetcherTraitWikiPath { 27 setSourcePath as protected setOriginalPathTrait; 28 } 29 30 const CANONICAL = "raster"; 31 const FAKE_LENGTH_FOR_BROKEN_IMAGES = 10; 32 33 34 private int $imageWidth; 35 private int $imageWeight; 36 37 38 /** 39 * @param string $imageId 40 * @param null $rev 41 * @return FetcherRaster 42 * @throws ExceptionBadArgument 43 * @throws ExceptionBadSyntax 44 * @throws ExceptionNotExists 45 */ 46 public static function createImageRasterFetchFromId(string $imageId, $rev = null): FetcherRaster 47 { 48 return FetcherRaster::createImageRasterFetchFromPath(WikiPath::createMediaPathFromId($imageId, $rev)); 49 } 50 51 /** 52 * @param Path $path 53 * @return FetcherRaster 54 * @throws ExceptionBadArgument 55 * @throws ExceptionBadSyntax 56 * @throws ExceptionNotExists 57 */ 58 public static function createImageRasterFetchFromPath(Path $path): FetcherRaster 59 { 60 $path = WikiPath::createFromPathObject($path); 61 return self::createEmptyRaster() 62 ->setSourcePath($path); 63 } 64 65 public static function createEmptyRaster(): FetcherRaster 66 { 67 return new FetcherRaster(); 68 } 69 70 /** 71 * @throws ExceptionBadArgument 72 */ 73 public static function createRasterFromFetchUrl(Url $fetchUrl): FetcherRaster 74 { 75 $fetchImageRaster = self::createEmptyRaster(); 76 $fetchImageRaster->buildFromUrl($fetchUrl); 77 return $fetchImageRaster; 78 } 79 80 81 /** 82 * @return int - the width of the image from the file 83 */ 84 public function getIntrinsicWidth(): int 85 { 86 return $this->imageWidth; 87 } 88 89 public function getFetchUrl(Url $url = null): Url 90 { 91 /** 92 * 93 * Because {@link FetcherRaster} does not create the image itself 94 * but dokuwiki does, we need to add the with and height dimension 95 * if the ratio is asked 96 * 97 * Before all other parent requirement such as 98 * ({@link FetcherImage::getTok()} uses them 99 * 100 * Note that we takes the target value 101 * before setting them otherwise it will affect the calculcation 102 * ie if we set the height and then calculatiing the target width, we will get 103 * a mini difference 104 * 105 */ 106 try { 107 $this->getRequestedAspectRatio(); 108 $targetHeight = $this->getTargetHeight(); 109 $targetWidth = $this->getTargetWidth(); 110 $this->setRequestedWidth($targetWidth); 111 $this->setRequestedHeight($targetHeight); 112 } catch (ExceptionNotFound $e) { 113 // 114 } 115 116 $url = parent::getFetchUrl($url); 117 118 /** 119 * Trait 120 */ 121 $this->addLocalPathParametersToFetchUrl($url, MediaMarkup::$MEDIA_QUERY_PARAMETER); 122 123 return $url; 124 } 125 126 127 /** 128 * @return int - the height of the image from the file 129 */ 130 public function getIntrinsicHeight(): int 131 { 132 return $this->imageWeight; 133 } 134 135 /** 136 * We check the existence of the file at build time 137 * because when we build the url, we make it at several breakpoints. 138 * We therefore needs the intrinsic dimension (height and weight) 139 * 140 * @throws ExceptionBadSyntax - if the path is not valid image format 141 */ 142 private 143 function analyzeImageIfNeeded() 144 { 145 146 if (!FileSystems::exists($this->getSourcePath())) { 147 // The user may type a bad path 148 // We don't throw as we want to be able to build 149 LogUtility::warning("The path ({$this->getSourcePath()}) does not exists"); 150 // broken image in the browser does not have any dimension 151 // todo: https://bitsofco.de/styling-broken-images/ 152 $this->imageWidth = self::FAKE_LENGTH_FOR_BROKEN_IMAGES; 153 $this->imageWeight = self::FAKE_LENGTH_FOR_BROKEN_IMAGES; 154 return; 155 } 156 157 /** 158 * Based on {@link media_image_preview_size()} 159 * $dimensions = media_image_preview_size($this->id, '', false); 160 */ 161 $path = $this->getSourcePath()->toLocalPath(); 162 $imageSize = getimagesize($path->toAbsolutePath()->toAbsoluteId()); 163 if ($imageSize === false) { 164 throw new ExceptionBadSyntax("We couldn't retrieve the type and dimensions of the image ($this). The image format seems to be not supported.", self::CANONICAL); 165 } 166 $this->imageWidth = (int)$imageSize[0]; 167 if (empty($this->imageWidth)) { 168 throw new ExceptionBadSyntax("We couldn't retrieve the width of the image ($this)", self::CANONICAL); 169 } 170 $this->imageWeight = (int)$imageSize[1]; 171 if (empty($this->imageWeight)) { 172 throw new ExceptionBadSyntax("We couldn't retrieve the height of the image ($this)", self::CANONICAL); 173 } 174 175 } 176 177 178 /** 179 * We overwrite the {@link FetcherTraitImage::getRequestedWidth()} 180 * because we don't scale up for raster image 181 * to not lose quality. 182 * 183 * @return int 184 * @throws ExceptionNotFound 185 */ 186 public 187 function getRequestedWidth(): int 188 { 189 190 /** 191 * Test, requested width should not be bigger than the media Height 192 * If this is the case, we return the media width 193 */ 194 $requestedWidth = parent::getRequestedWidth(); 195 196 /** 197 * A width was requested 198 */ 199 $mediaWidth = $this->getIntrinsicWidth(); 200 if ($requestedWidth > $mediaWidth) { 201 global $ID; 202 if ($ID !== "wiki:syntax") { 203 /** 204 * Info and not warning level because they fill the error log 205 * They don't really break anything and it's difficult 206 * to see when it's intended (ie there is no better image or not) 207 */ 208 // There is a bug in the wiki syntax page 209 // {{wiki:dokuwiki-128.png?200x50}} 210 // https://forum.dokuwiki.org/d/19313-bugtypo-how-to-make-a-request-to-change-the-syntax-page-on-dokuwikii 211 LogUtility::info("For the image ($this), the requested width of ($requestedWidth) can not be bigger than the intrinsic width of ($mediaWidth). The width was then set to its natural width ($mediaWidth)", self::CANONICAL); 212 } 213 return $mediaWidth; 214 } 215 216 return $requestedWidth; 217 218 } 219 220 221 /** 222 * 223 */ 224 public 225 function getTargetHeight(): int 226 { 227 228 try { 229 $requestedHeight = $this->getRequestedHeight(); 230 231 // it should not be bigger than the media Height 232 $mediaHeight = $this->getIntrinsicHeight(); 233 if ($requestedHeight > $mediaHeight) { 234 /** 235 * Info and not warning level because they fill the error log 236 * They don't really break anything and it's difficult 237 * to see when it's intended (ie there is no better image or not) 238 */ 239 LogUtility::info("For the image ($this), the requested height of ($requestedHeight) can not be bigger than the intrinsic height of ($mediaHeight). The height was then set to its natural height ($mediaHeight)", self::CANONICAL); 240 return $mediaHeight; 241 } 242 } catch (ExceptionNotFound $e) { 243 // no request height 244 } 245 return parent::getTargetHeight(); 246 247 248 } 249 250 public function getTargetWidth(): int 251 { 252 $targetWidth = parent::getTargetWidth(); 253 $intrinsicWidth = $this->getIntrinsicWidth(); 254 if ($targetWidth > $intrinsicWidth) { 255 /** 256 * Info and not warning level because they fill the error log 257 * They don't really break anything and it's difficult 258 * to see when it's intended (ie there is no better image or not) 259 */ 260 LogUtility::info("For the image ($this), the calculated width of ($targetWidth) cannot be bigger than the intrinsic width of ($targetWidth). The requested width was then set to its natural width ($intrinsicWidth).", self::CANONICAL); 261 return $intrinsicWidth; 262 } 263 return $targetWidth; 264 } 265 266 267 function getFetchPath(): LocalPath 268 { 269 /** 270 * In fetch.php 271 * if($HEIGHT && $WIDTH) { 272 * $data['file'] = $FILE = media_crop_image($data['file'], $EXT, $WIDTH, $HEIGHT); 273 * } else { 274 * $data['file'] = $FILE = media_resize_image($data['file'], $EXT, $WIDTH, $HEIGHT); 275 * } 276 */ 277 throw new ExceptionRuntime("Fetch Raster image is not yet implemented"); 278 } 279 280 281 /** 282 * @param TagAttributes $tagAttributes 283 * @return FetcherRaster 284 * @throws ExceptionBadArgument - if the path is not an image 285 * @throws ExceptionBadSyntax - if the image is badly encoded 286 * @throws ExceptionNotExists - if the image does not exists 287 */ 288 289 public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherImage 290 { 291 292 parent::buildFromTagAttributes($tagAttributes); 293 $this->buildOriginalPathFromTagAttributes($tagAttributes); 294 $this->analyzeImageIfNeeded(); 295 return $this; 296 297 } 298 299 /** 300 * @throws ExceptionBadSyntax - if the file is badly encoded 301 * @throws ExceptionNotExists - if the file does not exists 302 */ 303 public function setSourcePath(WikiPath $path): FetcherRaster 304 { 305 $this->setOriginalPathTrait($path); 306 $this->analyzeImageIfNeeded(); 307 return $this; 308 } 309 310 311 public function getFetcherName(): string 312 { 313 return self::CANONICAL; 314 } 315 316 public function __toString() 317 { 318 return $this->getSourcePath()->__toString(); 319 } 320 321 /** 322 * @return int 323 * @throws ExceptionNotFound 324 * We can upscale, we limit then the requested height to the internal size 325 */ 326 public function getRequestedHeight(): int 327 { 328 $requestedHeight = parent::getRequestedHeight(); 329 $intrinsicHeight = $this->getIntrinsicHeight(); 330 if ($requestedHeight > $intrinsicHeight) { 331 /** 332 * Info and not warning to not fill the log 333 * as it's pretty common with a {@link PageImageTag} 334 */ 335 LogUtility::info("For the image ($this), the requested height of ($requestedHeight) can not be bigger than the intrinsic height of ($intrinsicHeight). The height was then set to its natural height ($intrinsicHeight)", self::CANONICAL); 336 return $intrinsicHeight; 337 } 338 return $requestedHeight; 339 } 340 341 342} 343