1<?php
2
3
4namespace ComboStrap;
5
6
7use dokuwiki\Extension\SyntaxPlugin;
8use syntax_plugin_combo_button;
9use syntax_plugin_combo_link;
10use syntax_plugin_combo_pageimage;
11
12class Dimension
13{
14    /**
15     * The element that have an width and height
16     */
17    const NATURAL_SIZING_ELEMENT = [SvgImageLink::CANONICAL, RasterImageLink::CANONICAL];
18
19    const DESIGN_LAYOUT_CONSTRAINED = "constrained"; // fix value
20    const DESIGN_LAYOUT_FLUID = "fluid"; // adapt
21
22    /**
23     * On the width, if set, the design is fluid and will adapt to all screen
24     * with a min-width
25     */
26    const WIDTH_LAYOUT_DEFAULT = self::DESIGN_LAYOUT_FLUID;
27    /**
28     * On height, if set, the design is constrained and overflow
29     */
30    const HEIGHT_LAYOUT_DEFAULT = self::DESIGN_LAYOUT_CONSTRAINED;
31    const SCROLL = "scroll";
32
33    /**
34     * Logical height and width
35     * used by default to define the width and height of an image or a css box
36     */
37    const HEIGHT_KEY = 'height';
38    const WIDTH_KEY = 'width';
39
40    /**
41     * The ratio (16:9, ...) permits to change:
42     *   * the viewBox in svg
43     *   * the intrinsic dimension in raster
44     *
45     * It's then part of the request
46     * because in svg it is the definition of the viewBox
47     *
48     * The rendering function takes care of it
49     * and it's also passed in the fetch url
50     */
51    public const RATIO_ATTRIBUTE = "ratio";
52    const ZOOM_ATTRIBUTE = "zoom";
53
54
55    /**
56     * @param TagAttributes $attributes
57     */
58    public static function processWidthAndHeight(TagAttributes &$attributes)
59    {
60        $widthName = self::WIDTH_KEY;
61        if ($attributes->hasComponentAttribute($widthName)) {
62
63            $widthValue = trim($attributes->getValueAndRemove($widthName));
64
65            if ($widthValue == "0") {
66
67                /**
68                 * For an image, the dimension are restricted by height
69                 */
70                if ($attributes->hasComponentAttribute(self::HEIGHT_KEY)) {
71                    $attributes->addStyleDeclarationIfNotSet("width", "auto");
72                }
73
74            } else {
75
76
77                if ($widthValue == "fit") {
78                    $widthValue = "fit-content";
79                } else {
80                    /** Numeric value */
81                    $widthValue = TagAttributes::toQualifiedCssValue($widthValue);
82                }
83
84
85                /**
86                 * For an image (png, svg)
87                 * They have width and height **element** attribute
88                 */
89                if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) {
90
91                    /**
92                     * If the image is not ask as static resource (ie HTTP request)
93                     * but added in HTML
94                     * (ie {@link \action_plugin_combo_svg})
95                     */
96                    $requestedMime = $attributes->getMime();
97                    if ($requestedMime == TagAttributes::TEXT_HTML_MIME) {
98                        $attributes->addStyleDeclarationIfNotSet('max-width', $widthValue);
99                        $attributes->addStyleDeclarationIfNotSet('width', "100%");
100                    }
101
102                } else {
103
104                    /**
105                     * For a block
106                     */
107                    $attributes->addStyleDeclarationIfNotSet('max-width', $widthValue);
108
109                }
110            }
111
112        }
113
114        $heightName = self::HEIGHT_KEY;
115        if ($attributes->hasComponentAttribute($heightName)) {
116            $heightValue = trim($attributes->getValueAndRemove($heightName));
117            if ($heightValue !== "") {
118                $heightValue = TagAttributes::toQualifiedCssValue($heightValue);
119
120                if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) {
121
122                    /**
123                     * A element with a natural height is responsive, we set only the max-height
124                     *
125                     * By default, the image has a `height: auto` due to the img-fluid class
126                     * Making its height responsive
127                     */
128                    $attributes->addStyleDeclarationIfNotSet("max-height", $heightValue);
129
130                } else {
131
132                    /**
133                     * HTML Block
134                     *
135                     * Without the height value, a block display will collapse
136                     */
137                    if (self::HEIGHT_LAYOUT_DEFAULT == self::DESIGN_LAYOUT_CONSTRAINED) {
138
139                        /**
140                         * The box is constrained in height
141                         * By default, a box is not constrained
142                         */
143                        $attributes->addStyleDeclarationIfNotSet("height", $heightValue);
144
145                        $scrollMechanism = $attributes->getValueAndRemoveIfPresent("scroll");
146                        if ($scrollMechanism != null) {
147                            $scrollMechanism = trim(strtolower($scrollMechanism));
148                        }
149                        switch ($scrollMechanism) {
150                            case "toggle":
151                                // https://jsfiddle.net/gerardnico/h0g6xw58/
152                                $attributes->addStyleDeclarationIfNotSet("overflow-y", "hidden");
153                                $attributes->addStyleDeclarationIfNotSet("position", "relative");
154                                $attributes->addStyleDeclarationIfNotSet("display", "block");
155                                // The block should collapse to this height
156                                $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
157                                if ($attributes->hasComponentAttribute("id")) {
158                                    $id = $attributes->getValue("id");
159                                } else {
160                                    $id = $attributes->generateAndSetId();
161                                }
162                                /**
163                                 * Css of the button and other standard attribute
164                                 */
165                                PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot("height-toggle");
166                                /**
167                                 * Set the color dynamically to the color of the parent
168                                 */
169                                PluginUtility::getSnippetManager()->attachInternalJavascriptForSlot("height-toggle");
170                                /**
171                                 * The height when there is not the show class
172                                 * is the original height
173                                 */
174                                $css = <<<EOF
175#$id:not(.show){
176  height: $heightValue;
177  transition: height .35s ease;
178}
179EOF;
180                                PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot("height-toggle-show", $css);
181                                $bootstrapDataNameSpace = Bootstrap::getDataNamespace();
182                                $button = <<<EOF
183<button class="height-toggle-combo" data$bootstrapDataNameSpace-toggle="collapse" data$bootstrapDataNameSpace-target="#$id" aria-expanded="false"></button>
184EOF;
185
186                                $attributes->addHtmlAfterEnterTag($button);
187
188                                break;
189                            case "lift";
190                            default:
191                                $attributes->addStyleDeclarationIfNotSet("overflow", "auto");
192                                break;
193
194                        }
195
196
197                    } else {
198
199                        /**
200                         * if fluid
201                         * min-height and not height to not constraint the box
202                         */
203                        $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
204
205                    }
206                }
207            }
208
209        }
210    }
211
212    /**
213     *
214     * Toggle with a click on the collapsed element
215     * if there is no control element such as button or link inside
216     *
217     * This function is used at the {@link DOKU_LEXER_EXIT} state of a {@link SyntaxPlugin::handle()}
218     *
219     * @param CallStack $callStack
220     */
221    public static function addScrollToggleOnClickIfNoControl(CallStack $callStack)
222    {
223        $callStack->moveToEnd();
224        $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall();
225        $scrollAttribute = $openingCall->getAttribute(Dimension::SCROLL);
226        if ($scrollAttribute != null && $scrollAttribute == "toggle") {
227
228            $controlFound = false;
229            while ($actualCall = $callStack->next()) {
230                if (in_array($actualCall->getTagName(),
231                    [syntax_plugin_combo_button::TAG, syntax_plugin_combo_link::TAG, "internallink", "externallink"])) {
232                    $controlFound = true;
233                    break;
234                }
235            }
236            if (!$controlFound) {
237                $toggleOnClickId = "height-toggle-onclick";
238                PluginUtility::getSnippetManager()->attachInternalJavascriptForSlot($toggleOnClickId);
239                $openingCall->addClassName("{$toggleOnClickId}-combo");
240                $openingCall->addCssStyle("cursor", "pointer");
241            }
242
243        }
244    }
245
246    /**
247     * @param $value - a css value to a pixel
248     * @throws ExceptionCombo
249     */
250    public static function toPixelValue($value): int
251    {
252
253        preg_match("/[a-z]/i", $value, $matches, PREG_OFFSET_CAPTURE);
254        $unit = null;
255        if (sizeof($matches) > 0) {
256            $firstPosition = $matches[0][1];
257            $unit = strtolower(substr($value, $firstPosition));
258            $value = DataType::toFloat((substr($value, 0, $firstPosition)));
259        }
260        switch ($unit) {
261            case "rem":
262                $remValue = Site::getRem();
263                $targetValue = $value * $remValue;
264                break;
265            case "px":
266            default:
267                $targetValue = $value;
268        }
269        return DataType::toInteger($targetValue);
270    }
271
272    /**
273     * Convert 16:9, ... to a float
274     * @param string $stringRatio
275     * @return float
276     * @throws ExceptionCombo
277     */
278    public static function convertTextualRatioToNumber(string $stringRatio): float
279    {
280        list($width, $height) = explode(":", $stringRatio, 2);
281        try {
282            $width = DataType::toInteger($width);
283        } catch (ExceptionCombo $e) {
284            throw new ExceptionCombo("The width value ($width) of the ratio `$stringRatio` is not numeric", syntax_plugin_combo_pageimage::CANONICAL);
285        }
286        try {
287            $height = DataType::toInteger($height);
288        } catch (ExceptionCombo $e) {
289            throw new ExceptionCombo("The width value ($height) of the ratio `$stringRatio` is not numeric", syntax_plugin_combo_pageimage::CANONICAL);
290        }
291        if ($height == 0) {
292            throw new ExceptionCombo("The height value of the ratio `$stringRatio` should not be zero", syntax_plugin_combo_pageimage::CANONICAL);
293        }
294        return floatval($width / $height);
295
296    }
297
298
299}
300