xref: /plugin/combo/ComboStrap/FetcherRaster.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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, self::$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                // There is a bug in the wiki syntax page
204                // {{wiki:dokuwiki-128.png?200x50}}
205                // https://forum.dokuwiki.org/d/19313-bugtypo-how-to-make-a-request-to-change-the-syntax-page-on-dokuwikii
206                LogUtility::warning("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);
207            }
208            return $mediaWidth;
209        }
210
211        return $requestedWidth;
212
213    }
214
215
216    /**
217     *
218     */
219    public
220    function getTargetHeight(): int
221    {
222
223        try {
224            $requestedHeight = $this->getRequestedHeight();
225
226            // it should not be bigger than the media Height
227            $mediaHeight = $this->getIntrinsicHeight();
228            if ($requestedHeight > $mediaHeight) {
229                LogUtility::warning("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);
230                return $mediaHeight;
231            }
232        } catch (ExceptionNotFound $e) {
233            // no request height
234        }
235        return parent::getTargetHeight();
236
237
238    }
239
240
241    function getFetchPath(): LocalPath
242    {
243        /**
244         * In fetch.php
245         * if($HEIGHT && $WIDTH) {
246         *    $data['file'] = $FILE = media_crop_image($data['file'], $EXT, $WIDTH, $HEIGHT);
247         * } else {
248         *    $data['file'] = $FILE = media_resize_image($data['file'], $EXT, $WIDTH, $HEIGHT);
249         * }
250         */
251        throw new ExceptionRuntime("Fetch Raster image is not yet implemented");
252    }
253
254
255    /**
256     * @param TagAttributes $tagAttributes
257     * @return FetcherRaster
258     * @throws ExceptionBadArgument - if the path is not an image
259     * @throws ExceptionBadSyntax - if the image is badly encoded
260     * @throws ExceptionNotExists - if the image does not exists
261     */
262
263    public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherImage
264    {
265
266        parent::buildFromTagAttributes($tagAttributes);
267        $this->buildOriginalPathFromTagAttributes($tagAttributes);
268        $this->analyzeImageIfNeeded();
269        return $this;
270
271    }
272
273    /**
274     * @throws ExceptionBadSyntax - if the file is badly encoded
275     * @throws ExceptionNotExists - if the file does not exists
276     */
277    public function setSourcePath(WikiPath $path): FetcherRaster
278    {
279        $this->setOriginalPathTrait($path);
280        $this->analyzeImageIfNeeded();
281        return $this;
282    }
283
284
285    public function getFetcherName(): string
286    {
287        return self::CANONICAL;
288    }
289
290    public function __toString()
291    {
292        return $this->getSourcePath()->__toString();
293    }
294
295
296}
297