1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeaunamespace ComboStrap; 4*04fd306cSNickeau 5*04fd306cSNickeauuse ComboStrap\Web\Url; 6*04fd306cSNickeau 7*04fd306cSNickeau/** 8*04fd306cSNickeau * Image request / response 9*04fd306cSNickeau * 10*04fd306cSNickeau * with requested attribute (ie a file and its transformation attribute if any such as 11*04fd306cSNickeau * width, height, ...) 12*04fd306cSNickeau * 13*04fd306cSNickeau * Image may be generated that's why they don't extends {@link FetcherRawLocalPath}. 14*04fd306cSNickeau * Image that depends on a source file use the {@link FetcherTraitWikiPath} and extends {@link IFetcherLocalImage} 15*04fd306cSNickeau * 16*04fd306cSNickeau * See also third provider such as: 17*04fd306cSNickeau * * https://docs.imgix.com/setup/quick-start - still need to host them (https://docs.imgix.com/apis/rendering) 18*04fd306cSNickeau * 19*04fd306cSNickeau * 20*04fd306cSNickeau * 21*04fd306cSNickeau */ 22*04fd306cSNickeauabstract class FetcherImage extends IFetcherAbs implements IFetcherPath 23*04fd306cSNickeau{ 24*04fd306cSNickeau 25*04fd306cSNickeau const TOK = "tok"; 26*04fd306cSNickeau const CANONICAL = "image"; 27*04fd306cSNickeau 28*04fd306cSNickeau 29*04fd306cSNickeau protected ?int $requestedWidth = null; 30*04fd306cSNickeau protected ?int $requestedHeight = null; 31*04fd306cSNickeau 32*04fd306cSNickeau private ?string $requestedRatio = null; 33*04fd306cSNickeau private ?float $requestedRatioAsFloat = null; 34*04fd306cSNickeau 35*04fd306cSNickeau 36*04fd306cSNickeau /** 37*04fd306cSNickeau * Image Fetch constructor. 38*04fd306cSNickeau * 39*04fd306cSNickeau */ 40*04fd306cSNickeau public function __construct() 41*04fd306cSNickeau { 42*04fd306cSNickeau /** 43*04fd306cSNickeau * Image can be generated, ie {@link FetcherVignette}, {@link FetcherScreenshot} 44*04fd306cSNickeau */ 45*04fd306cSNickeau } 46*04fd306cSNickeau 47*04fd306cSNickeau 48*04fd306cSNickeau /** 49*04fd306cSNickeau * @param Url|null $url 50*04fd306cSNickeau * 51*04fd306cSNickeau */ 52*04fd306cSNickeau public function getFetchUrl(Url $url = null): Url 53*04fd306cSNickeau { 54*04fd306cSNickeau $url = parent::getFetchUrl($url); 55*04fd306cSNickeau 56*04fd306cSNickeau try { 57*04fd306cSNickeau $ratio = $this->getRequestedAspectRatio(); 58*04fd306cSNickeau $url->addQueryParameterIfNotPresent(Dimension::RATIO_ATTRIBUTE, $ratio); 59*04fd306cSNickeau } catch (ExceptionNotFound $e) { 60*04fd306cSNickeau // no width ok 61*04fd306cSNickeau } 62*04fd306cSNickeau 63*04fd306cSNickeau try { 64*04fd306cSNickeau $requestedWidth = $this->getRequestedWidth(); 65*04fd306cSNickeau $url->addQueryParameterIfNotPresent(Dimension::WIDTH_KEY_SHORT, $requestedWidth); 66*04fd306cSNickeau } catch (ExceptionNotFound $e) { 67*04fd306cSNickeau // no width ok 68*04fd306cSNickeau } 69*04fd306cSNickeau 70*04fd306cSNickeau try { 71*04fd306cSNickeau $requestedHeight = $this->getRequestedHeight(); 72*04fd306cSNickeau $url->addQueryParameterIfNotPresent(Dimension::HEIGHT_KEY_SHORT, $requestedHeight); 73*04fd306cSNickeau } catch (ExceptionNotFound $e) { 74*04fd306cSNickeau // no height ok 75*04fd306cSNickeau } 76*04fd306cSNickeau 77*04fd306cSNickeau 78*04fd306cSNickeau /** 79*04fd306cSNickeau * Dokuwiki Conformance 80*04fd306cSNickeau */ 81*04fd306cSNickeau try { 82*04fd306cSNickeau $url->addQueryParameter(FetcherImage::TOK, $this->getTok()); 83*04fd306cSNickeau } catch (ExceptionNotNeeded $e) { 84*04fd306cSNickeau // ok not needed 85*04fd306cSNickeau } 86*04fd306cSNickeau 87*04fd306cSNickeau 88*04fd306cSNickeau return $url; 89*04fd306cSNickeau } 90*04fd306cSNickeau 91*04fd306cSNickeau /** 92*04fd306cSNickeau * The tok is supposed to counter a DDOS attack when 93*04fd306cSNickeau * with or height are requested 94*04fd306cSNickeau * 95*04fd306cSNickeau * 96*04fd306cSNickeau * @throws ExceptionNotNeeded 97*04fd306cSNickeau */ 98*04fd306cSNickeau public function getTok(): string 99*04fd306cSNickeau { 100*04fd306cSNickeau /** 101*04fd306cSNickeau * Dokuwiki Compliance 102*04fd306cSNickeau */ 103*04fd306cSNickeau if (!($this instanceof IFetcherLocalImage)) { 104*04fd306cSNickeau throw new ExceptionNotNeeded("No tok for non local image"); 105*04fd306cSNickeau } 106*04fd306cSNickeau try { 107*04fd306cSNickeau $requestedWidth = $this->getRequestedWidth(); 108*04fd306cSNickeau } catch (ExceptionNotFound $e) { 109*04fd306cSNickeau $requestedWidth = null; 110*04fd306cSNickeau } 111*04fd306cSNickeau try { 112*04fd306cSNickeau $requestedHeight = $this->getRequestedHeight(); 113*04fd306cSNickeau } catch (ExceptionNotFound $e) { 114*04fd306cSNickeau $requestedHeight = null; 115*04fd306cSNickeau } 116*04fd306cSNickeau if ($requestedWidth !== null || $requestedHeight !== null) { 117*04fd306cSNickeau 118*04fd306cSNickeau try { 119*04fd306cSNickeau $id = $this->getSourcePath()->toWikiPath()->getWikiId(); 120*04fd306cSNickeau } catch (ExceptionCast $e) { 121*04fd306cSNickeau LogUtility::error("Unable to calculate the image tok. The source path is not a web/wiki path", self::CANONICAL, $e); 122*04fd306cSNickeau throw new ExceptionNotNeeded("No tok added, error " . $e->getMessage()); 123*04fd306cSNickeau } 124*04fd306cSNickeau return media_get_token($id, $requestedWidth, $requestedHeight); 125*04fd306cSNickeau 126*04fd306cSNickeau } 127*04fd306cSNickeau throw new ExceptionNotNeeded("No tok needed"); 128*04fd306cSNickeau } 129*04fd306cSNickeau 130*04fd306cSNickeau /** 131*04fd306cSNickeau * @throws ExceptionBadArgument 132*04fd306cSNickeau */ 133*04fd306cSNickeau public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherImage 134*04fd306cSNickeau { 135*04fd306cSNickeau 136*04fd306cSNickeau $requestedWidth = $tagAttributes->getValueAndRemove(Dimension::WIDTH_KEY); 137*04fd306cSNickeau if ($requestedWidth === null) { 138*04fd306cSNickeau $requestedWidth = $tagAttributes->getValueAndRemove(Dimension::WIDTH_KEY_SHORT); 139*04fd306cSNickeau } 140*04fd306cSNickeau if ($requestedWidth !== null) { 141*04fd306cSNickeau try { 142*04fd306cSNickeau $requestedWidthInt = DataType::toInteger(ConditionalLength::createFromString($requestedWidth)->toPixelNumber()); 143*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 144*04fd306cSNickeau throw new ExceptionBadArgument("The width value ($requestedWidth) is not a valid integer", FetcherImage::CANONICAL, 0, $e); 145*04fd306cSNickeau } 146*04fd306cSNickeau $this->setRequestedWidth($requestedWidthInt); 147*04fd306cSNickeau } 148*04fd306cSNickeau 149*04fd306cSNickeau $requestedHeight = $tagAttributes->getValueAndRemove(Dimension::HEIGHT_KEY); 150*04fd306cSNickeau if ($requestedHeight === null) { 151*04fd306cSNickeau $requestedHeight = $tagAttributes->getValueAndRemove(Dimension::HEIGHT_KEY_SHORT); 152*04fd306cSNickeau } 153*04fd306cSNickeau if ($requestedHeight !== null) { 154*04fd306cSNickeau try { 155*04fd306cSNickeau $requestedHeightInt = DataType::toInteger($requestedHeight); 156*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 157*04fd306cSNickeau throw new ExceptionBadArgument("The height value ($requestedHeight) is not a valid integer", FetcherImage::CANONICAL, 0, $e); 158*04fd306cSNickeau } 159*04fd306cSNickeau $this->setRequestedHeight($requestedHeightInt); 160*04fd306cSNickeau } 161*04fd306cSNickeau 162*04fd306cSNickeau $requestedRatio = $tagAttributes->getValueAndRemove(Dimension::RATIO_ATTRIBUTE); 163*04fd306cSNickeau if ($requestedRatio !== null) { 164*04fd306cSNickeau try { 165*04fd306cSNickeau $this->setRequestedAspectRatio($requestedRatio); 166*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 167*04fd306cSNickeau throw new ExceptionBadArgument("The requested ratio ($requestedRatio) is not a valid value ({$e->getMessage()})", FetcherImage::CANONICAL, 0, $e); 168*04fd306cSNickeau } 169*04fd306cSNickeau } 170*04fd306cSNickeau parent::buildFromTagAttributes($tagAttributes); 171*04fd306cSNickeau return $this; 172*04fd306cSNickeau } 173*04fd306cSNickeau 174*04fd306cSNickeau 175*04fd306cSNickeau /** 176*04fd306cSNickeau * For a raster image, the internal width 177*04fd306cSNickeau * for a svg, the defined viewBox 178*04fd306cSNickeau * 179*04fd306cSNickeau * @return int in pixel 180*04fd306cSNickeau */ 181*04fd306cSNickeau public 182*04fd306cSNickeau 183*04fd306cSNickeau abstract function getIntrinsicWidth(): int; 184*04fd306cSNickeau 185*04fd306cSNickeau /** 186*04fd306cSNickeau * For a raster image, the internal height 187*04fd306cSNickeau * for a svg, the defined `viewBox` value 188*04fd306cSNickeau * 189*04fd306cSNickeau * This is needed to calculate the {@link MediaLink::getTargetRatio() target ratio} 190*04fd306cSNickeau * and pass them to the img tag to avoid layout shift 191*04fd306cSNickeau * 192*04fd306cSNickeau * @return int in pixel 193*04fd306cSNickeau */ 194*04fd306cSNickeau public abstract function getIntrinsicHeight(): int; 195*04fd306cSNickeau 196*04fd306cSNickeau /** 197*04fd306cSNickeau * The Aspect ratio as explained here 198*04fd306cSNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 199*04fd306cSNickeau * @return float 200*04fd306cSNickeau * false if the image is not supported 201*04fd306cSNickeau * 202*04fd306cSNickeau * It's needed for an img tag to set the img `width` and `height` that pass the 203*04fd306cSNickeau * {@link MediaLink::checkWidthAndHeightRatioAndReturnTheGoodValue() check} 204*04fd306cSNickeau * to avoid layout shift 205*04fd306cSNickeau * 206*04fd306cSNickeau */ 207*04fd306cSNickeau public function getIntrinsicAspectRatio(): float 208*04fd306cSNickeau { 209*04fd306cSNickeau 210*04fd306cSNickeau return $this->getIntrinsicWidth() / $this->getIntrinsicHeight(); 211*04fd306cSNickeau 212*04fd306cSNickeau } 213*04fd306cSNickeau 214*04fd306cSNickeau /** 215*04fd306cSNickeau * The Aspect ratio of the target image (may be the original or the an image scaled down) 216*04fd306cSNickeau * 217*04fd306cSNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 218*04fd306cSNickeau * @return float 219*04fd306cSNickeau * false if the image is not supported 220*04fd306cSNickeau * 221*04fd306cSNickeau * It's needed for an img tag to set the img `width` and `height` that pass the 222*04fd306cSNickeau * {@link MediaLink::checkWidthAndHeightRatioAndReturnTheGoodValue() check} 223*04fd306cSNickeau * to avoid layout shift 224*04fd306cSNickeau * 225*04fd306cSNickeau */ 226*04fd306cSNickeau public function getTargetAspectRatio() 227*04fd306cSNickeau { 228*04fd306cSNickeau 229*04fd306cSNickeau return $this->getTargetWidth() / $this->getTargetHeight(); 230*04fd306cSNickeau 231*04fd306cSNickeau } 232*04fd306cSNickeau 233*04fd306cSNickeau /** 234*04fd306cSNickeau * Return the requested aspect ratio requested 235*04fd306cSNickeau * with the property 236*04fd306cSNickeau * or if the width and height were specified. 237*04fd306cSNickeau * 238*04fd306cSNickeau * The Aspect ratio as explained here 239*04fd306cSNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 240*04fd306cSNickeau * @return float 241*04fd306cSNickeau * 242*04fd306cSNickeau * 243*04fd306cSNickeau * It's needed for an img tag to set the img `width` and `height` that pass the 244*04fd306cSNickeau * {@link MediaLink::checkWidthAndHeightRatioAndReturnTheGoodValue() check} 245*04fd306cSNickeau * to avoid layout shift 246*04fd306cSNickeau * @throws ExceptionNotFound 247*04fd306cSNickeau */ 248*04fd306cSNickeau public function getCalculatedRequestedAspectRatioAsFloat(): float 249*04fd306cSNickeau { 250*04fd306cSNickeau 251*04fd306cSNickeau if ($this->requestedRatioAsFloat !== null) { 252*04fd306cSNickeau return $this->requestedRatioAsFloat; 253*04fd306cSNickeau } 254*04fd306cSNickeau 255*04fd306cSNickeau /** 256*04fd306cSNickeau * Note: requested weight and width throw a `not found` if width / height == 0 257*04fd306cSNickeau * No division by zero then 258*04fd306cSNickeau */ 259*04fd306cSNickeau return $this->getRequestedWidth() / $this->getRequestedHeight(); 260*04fd306cSNickeau 261*04fd306cSNickeau 262*04fd306cSNickeau } 263*04fd306cSNickeau 264*04fd306cSNickeau 265*04fd306cSNickeau /** 266*04fd306cSNickeau * Giving width and height, check that the aspect ratio is the same 267*04fd306cSNickeau * than the target one 268*04fd306cSNickeau * @param $height 269*04fd306cSNickeau * @param $width 270*04fd306cSNickeau */ 271*04fd306cSNickeau public 272*04fd306cSNickeau function checkLogicalRatioAgainstTargetRatio($width, $height) 273*04fd306cSNickeau { 274*04fd306cSNickeau /** 275*04fd306cSNickeau * Check of height and width dimension 276*04fd306cSNickeau * as specified here 277*04fd306cSNickeau * 278*04fd306cSNickeau * This is about the intrinsic dimension but we have the notion of target dimension 279*04fd306cSNickeau * 280*04fd306cSNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 281*04fd306cSNickeau */ 282*04fd306cSNickeau try { 283*04fd306cSNickeau $targetRatio = $this->getTargetAspectRatio(); 284*04fd306cSNickeau } catch (ExceptionCompile $e) { 285*04fd306cSNickeau LogUtility::msg("Unable to check the target ratio because it returns this error: {$e->getMessage()}"); 286*04fd306cSNickeau return; 287*04fd306cSNickeau } 288*04fd306cSNickeau if (!( 289*04fd306cSNickeau $height * $targetRatio >= $width - 1 290*04fd306cSNickeau && 291*04fd306cSNickeau $height * $targetRatio <= $width + 1 292*04fd306cSNickeau )) { 293*04fd306cSNickeau // check the second statement 294*04fd306cSNickeau if (!( 295*04fd306cSNickeau $width / $targetRatio >= $height - 1 296*04fd306cSNickeau && 297*04fd306cSNickeau $width / $targetRatio <= $height + 1 298*04fd306cSNickeau )) { 299*04fd306cSNickeau 300*04fd306cSNickeau /** 301*04fd306cSNickeau * Programmatic error from the developer 302*04fd306cSNickeau */ 303*04fd306cSNickeau $imgTagRatio = $width / $height; 304*04fd306cSNickeau LogUtility::msg("Internal Error: The width ($width) and height ($height) calculated for the image ($this) does not pass the ratio test. They have a ratio of ($imgTagRatio) while the target dimension ratio is ($targetRatio)"); 305*04fd306cSNickeau 306*04fd306cSNickeau } 307*04fd306cSNickeau } 308*04fd306cSNickeau } 309*04fd306cSNickeau 310*04fd306cSNickeau 311*04fd306cSNickeau /** 312*04fd306cSNickeau * The logical height is the calculated height of the target image 313*04fd306cSNickeau * specified in the query parameters 314*04fd306cSNickeau * 315*04fd306cSNickeau * For instance, 316*04fd306cSNickeau * * with `200`, the target image has a {@link FetcherTraitImage::getTargetWidth() logical width} of 200 and a {@link FetcherTraitImage::getTargetHeight() logical height} that is scaled down by the {@link FetcherTraitImage::getIntrinsicAspectRatio() instrinsic ratio} 317*04fd306cSNickeau * * with ''0x20'', the target image has a {@link FetcherTraitImage::getTargetHeight() logical height} of 20 and a {@link FetcherTraitImage::getTargetWidth() logical width} that is scaled down by the {@link FetcherTraitImage::getIntrinsicAspectRatio() instrinsic ratio} 318*04fd306cSNickeau * 319*04fd306cSNickeau * The doc is {@link https://www.dokuwiki.org/images#resizing} 320*04fd306cSNickeau * 321*04fd306cSNickeau * 322*04fd306cSNickeau * @return int 323*04fd306cSNickeau */ 324*04fd306cSNickeau public function getTargetHeight(): int 325*04fd306cSNickeau { 326*04fd306cSNickeau 327*04fd306cSNickeau try { 328*04fd306cSNickeau return $this->getRequestedHeight(); 329*04fd306cSNickeau } catch (ExceptionNotFound $e) { 330*04fd306cSNickeau // no height 331*04fd306cSNickeau } 332*04fd306cSNickeau 333*04fd306cSNickeau /** 334*04fd306cSNickeau * Scaled down by width 335*04fd306cSNickeau */ 336*04fd306cSNickeau try { 337*04fd306cSNickeau $width = $this->getRequestedWidth(); 338*04fd306cSNickeau try { 339*04fd306cSNickeau $ratio = $this->getCalculatedRequestedAspectRatioAsFloat(); 340*04fd306cSNickeau } catch (ExceptionNotFound $e) { 341*04fd306cSNickeau $ratio = $this->getIntrinsicAspectRatio(); 342*04fd306cSNickeau } 343*04fd306cSNickeau return self::round($width / $ratio); 344*04fd306cSNickeau } catch (ExceptionNotFound $e) { 345*04fd306cSNickeau // no width 346*04fd306cSNickeau } 347*04fd306cSNickeau 348*04fd306cSNickeau 349*04fd306cSNickeau /** 350*04fd306cSNickeau * Scaled down by ratio 351*04fd306cSNickeau */ 352*04fd306cSNickeau try { 353*04fd306cSNickeau $ratio = $this->getCalculatedRequestedAspectRatioAsFloat(); 354*04fd306cSNickeau [$croppedWidth, $croppedHeight] = $this->getCroppingDimensionsWithRatio($ratio); 355*04fd306cSNickeau return $croppedHeight; 356*04fd306cSNickeau } catch (ExceptionNotFound $e) { 357*04fd306cSNickeau // no requested aspect ratio 358*04fd306cSNickeau } 359*04fd306cSNickeau 360*04fd306cSNickeau return $this->getIntrinsicHeight(); 361*04fd306cSNickeau 362*04fd306cSNickeau } 363*04fd306cSNickeau 364*04fd306cSNickeau /** 365*04fd306cSNickeau * The logical width is the width of the target image calculated from the requested dimension 366*04fd306cSNickeau * 367*04fd306cSNickeau * For instance, 368*04fd306cSNickeau * * with `200`, the target image has a {@link FetcherTraitImage::getTargetWidth() logical width} of 200 and a {@link FetcherTraitImage::getTargetHeight() logical height} that is scaled down by the {@link FetcherTraitImage::getIntrinsicAspectRatio() instrinsic ratio} 369*04fd306cSNickeau * * with ''0x20'', the target image has a {@link FetcherTraitImage::getTargetHeight() logical height} of 20 and a {@link FetcherTraitImage::getTargetWidth() logical width} that is scaled down by the {@link FetcherTraitImage::getIntrinsicAspectRatio() instrinsic ratio} 370*04fd306cSNickeau * 371*04fd306cSNickeau * The doc is {@link https://www.dokuwiki.org/images#resizing} 372*04fd306cSNickeau * @return int 373*04fd306cSNickeau */ 374*04fd306cSNickeau public function getTargetWidth(): int 375*04fd306cSNickeau { 376*04fd306cSNickeau 377*04fd306cSNickeau try { 378*04fd306cSNickeau return $this->getRequestedWidth(); 379*04fd306cSNickeau } catch (ExceptionNotFound $e) { 380*04fd306cSNickeau // no requested width 381*04fd306cSNickeau } 382*04fd306cSNickeau 383*04fd306cSNickeau /** 384*04fd306cSNickeau * Scaled down by Height 385*04fd306cSNickeau */ 386*04fd306cSNickeau try { 387*04fd306cSNickeau $height = $this->getRequestedHeight(); 388*04fd306cSNickeau try { 389*04fd306cSNickeau $ratio = $this->getCalculatedRequestedAspectRatioAsFloat(); 390*04fd306cSNickeau } catch (ExceptionNotFound $e) { 391*04fd306cSNickeau $ratio = $this->getIntrinsicAspectRatio(); 392*04fd306cSNickeau } 393*04fd306cSNickeau return self::round($ratio * $height); 394*04fd306cSNickeau } catch (ExceptionNotFound $e) { 395*04fd306cSNickeau // no requested height 396*04fd306cSNickeau } 397*04fd306cSNickeau 398*04fd306cSNickeau 399*04fd306cSNickeau /** 400*04fd306cSNickeau * Scaled down by Ratio 401*04fd306cSNickeau */ 402*04fd306cSNickeau try { 403*04fd306cSNickeau $ratio = $this->getCalculatedRequestedAspectRatioAsFloat(); 404*04fd306cSNickeau [$logicalWidthWithRatio, $logicalHeightWithRatio] = $this->getCroppingDimensionsWithRatio($ratio); 405*04fd306cSNickeau return $logicalWidthWithRatio; 406*04fd306cSNickeau } catch (ExceptionNotFound $e) { 407*04fd306cSNickeau // no ratio requested 408*04fd306cSNickeau } 409*04fd306cSNickeau 410*04fd306cSNickeau return $this->getIntrinsicWidth(); 411*04fd306cSNickeau 412*04fd306cSNickeau } 413*04fd306cSNickeau 414*04fd306cSNickeau /** 415*04fd306cSNickeau * @return int|null 416*04fd306cSNickeau * @throws ExceptionNotFound - if no requested width was asked 417*04fd306cSNickeau */ 418*04fd306cSNickeau public function getRequestedWidth(): int 419*04fd306cSNickeau { 420*04fd306cSNickeau if ($this->requestedWidth === null) { 421*04fd306cSNickeau throw new ExceptionNotFound("No width was requested"); 422*04fd306cSNickeau } 423*04fd306cSNickeau if ($this->requestedWidth === 0) { 424*04fd306cSNickeau throw new ExceptionNotFound("Width 0 was requested"); 425*04fd306cSNickeau } 426*04fd306cSNickeau return $this->requestedWidth; 427*04fd306cSNickeau } 428*04fd306cSNickeau 429*04fd306cSNickeau /** 430*04fd306cSNickeau * @return int 431*04fd306cSNickeau * @throws ExceptionNotFound - if no requested height was asked 432*04fd306cSNickeau */ 433*04fd306cSNickeau public function getRequestedHeight(): int 434*04fd306cSNickeau { 435*04fd306cSNickeau if ($this->requestedHeight === null) { 436*04fd306cSNickeau throw new ExceptionNotFound("Height not requested"); 437*04fd306cSNickeau } 438*04fd306cSNickeau if ($this->requestedHeight === 0) { 439*04fd306cSNickeau throw new ExceptionNotFound("Height 0 requested"); 440*04fd306cSNickeau } 441*04fd306cSNickeau return $this->requestedHeight; 442*04fd306cSNickeau } 443*04fd306cSNickeau 444*04fd306cSNickeau /** 445*04fd306cSNickeau * Rounding to integer 446*04fd306cSNickeau * The fetch.php file takes int as value for width and height 447*04fd306cSNickeau * making a rounding if we pass a double (such as 37.5) 448*04fd306cSNickeau * This is important because the security token is based on width and height 449*04fd306cSNickeau * and therefore the fetch will failed 450*04fd306cSNickeau * 451*04fd306cSNickeau * And not directly {@link intval} because it will make from 3.6, 3 and not 4 452*04fd306cSNickeau * 453*04fd306cSNickeau * And this is also ask by the specification 454*04fd306cSNickeau * a non-null positive integer 455*04fd306cSNickeau * https://html.spec.whatwg.org/multipage/embedded-content-other.html#attr-dim-height 456*04fd306cSNickeau * 457*04fd306cSNickeau */ 458*04fd306cSNickeau public static function round(float $param): int 459*04fd306cSNickeau { 460*04fd306cSNickeau return intval(round($param)); 461*04fd306cSNickeau } 462*04fd306cSNickeau 463*04fd306cSNickeau 464*04fd306cSNickeau /** 465*04fd306cSNickeau * 466*04fd306cSNickeau * Return the width and height of the image 467*04fd306cSNickeau * after applying a ratio (16x9, 4x3, ..) 468*04fd306cSNickeau * 469*04fd306cSNickeau * The new dimension will apply to: 470*04fd306cSNickeau * * the viewBox for svg 471*04fd306cSNickeau * * the physical dimension for raster image 472*04fd306cSNickeau * 473*04fd306cSNickeau */ 474*04fd306cSNickeau public function getCroppingDimensionsWithRatio(float $targetRatio): array 475*04fd306cSNickeau { 476*04fd306cSNickeau 477*04fd306cSNickeau /** 478*04fd306cSNickeau * Trying to crop on the width 479*04fd306cSNickeau */ 480*04fd306cSNickeau $logicalWidth = $this->getIntrinsicWidth(); 481*04fd306cSNickeau $logicalHeight = $this->round($logicalWidth / $targetRatio); 482*04fd306cSNickeau if ($logicalHeight > $this->getIntrinsicHeight()) { 483*04fd306cSNickeau /** 484*04fd306cSNickeau * Cropping by height 485*04fd306cSNickeau */ 486*04fd306cSNickeau $logicalHeight = $this->getIntrinsicHeight(); 487*04fd306cSNickeau $logicalWidth = $this->round($targetRatio * $logicalHeight); 488*04fd306cSNickeau } 489*04fd306cSNickeau return [$logicalWidth, $logicalHeight]; 490*04fd306cSNickeau 491*04fd306cSNickeau } 492*04fd306cSNickeau 493*04fd306cSNickeau 494*04fd306cSNickeau public function setRequestedWidth(int $requestedWidth): FetcherImage 495*04fd306cSNickeau { 496*04fd306cSNickeau $this->requestedWidth = $requestedWidth; 497*04fd306cSNickeau return $this; 498*04fd306cSNickeau } 499*04fd306cSNickeau 500*04fd306cSNickeau public function setRequestedHeight(int $requestedHeight): FetcherImage 501*04fd306cSNickeau { 502*04fd306cSNickeau $this->requestedHeight = $requestedHeight; 503*04fd306cSNickeau return $this; 504*04fd306cSNickeau } 505*04fd306cSNickeau 506*04fd306cSNickeau /** 507*04fd306cSNickeau * @throws ExceptionBadSyntax 508*04fd306cSNickeau */ 509*04fd306cSNickeau public function setRequestedAspectRatio(string $requestedRatio): FetcherImage 510*04fd306cSNickeau { 511*04fd306cSNickeau $this->requestedRatio = $requestedRatio; 512*04fd306cSNickeau $this->requestedRatioAsFloat = Dimension::convertTextualRatioToNumber($requestedRatio); 513*04fd306cSNickeau return $this; 514*04fd306cSNickeau } 515*04fd306cSNickeau 516*04fd306cSNickeau 517*04fd306cSNickeau public function __toString() 518*04fd306cSNickeau { 519*04fd306cSNickeau return get_class($this); 520*04fd306cSNickeau } 521*04fd306cSNickeau 522*04fd306cSNickeau 523*04fd306cSNickeau public function hasHeightRequested(): bool 524*04fd306cSNickeau { 525*04fd306cSNickeau try { 526*04fd306cSNickeau $this->getRequestedHeight(); 527*04fd306cSNickeau return true; 528*04fd306cSNickeau } catch (ExceptionNotFound $e) { 529*04fd306cSNickeau return false; 530*04fd306cSNickeau } 531*04fd306cSNickeau } 532*04fd306cSNickeau 533*04fd306cSNickeau public function hasAspectRatioRequested(): bool 534*04fd306cSNickeau { 535*04fd306cSNickeau try { 536*04fd306cSNickeau $this->getCalculatedRequestedAspectRatioAsFloat(); 537*04fd306cSNickeau return true; 538*04fd306cSNickeau } catch (ExceptionNotFound $e) { 539*04fd306cSNickeau return false; 540*04fd306cSNickeau } 541*04fd306cSNickeau 542*04fd306cSNickeau } 543*04fd306cSNickeau 544*04fd306cSNickeau 545*04fd306cSNickeau /** 546*04fd306cSNickeau * @throws ExceptionNotFound 547*04fd306cSNickeau */ 548*04fd306cSNickeau public function getRequestedAspectRatio(): string 549*04fd306cSNickeau { 550*04fd306cSNickeau if ($this->requestedRatio === null) { 551*04fd306cSNickeau throw new ExceptionNotFound("No ratio was specified"); 552*04fd306cSNickeau } 553*04fd306cSNickeau return $this->requestedRatio; 554*04fd306cSNickeau } 555*04fd306cSNickeau 556*04fd306cSNickeau public function isCropRequested(): bool 557*04fd306cSNickeau { 558*04fd306cSNickeau if ($this->requestedHeight !== null && $this->requestedWidth !== null) { 559*04fd306cSNickeau return true; 560*04fd306cSNickeau } 561*04fd306cSNickeau if ($this->requestedRatio != null) { 562*04fd306cSNickeau return true; 563*04fd306cSNickeau } 564*04fd306cSNickeau return false; 565*04fd306cSNickeau } 566*04fd306cSNickeau 567*04fd306cSNickeau 568*04fd306cSNickeau} 569