xref: /plugin/combo/ComboStrap/Dimension.php (revision 82a60d039cd81033dc8147c27f0a50716b7a5301)
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
53
54    /**
55     * @param TagAttributes $attributes
56     */
57    public static function processWidthAndHeight(&$attributes)
58    {
59        $widthName = self::WIDTH_KEY;
60        if ($attributes->hasComponentAttribute($widthName)) {
61
62            $widthValue = trim($attributes->getValueAndRemove($widthName));
63
64            if ($widthValue == "0") {
65
66                /**
67                 * For an image, the dimension are restricted by height
68                 */
69                if ($attributes->hasComponentAttribute(self::HEIGHT_KEY)) {
70                    $attributes->addStyleDeclarationIfNotSet("width", "auto");
71                }
72
73            } else {
74
75
76                if ($widthValue == "fit") {
77                    $widthValue = "fit-content";
78                } else {
79                    /** Numeric value */
80                    $widthValue = TagAttributes::toQualifiedCssValue($widthValue);
81                }
82
83
84                /**
85                 * For an image (png, svg)
86                 * They have width and height **element** attribute
87                 */
88                if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) {
89
90                    /**
91                     * If the image is not ask as static resource (ie HTTP request)
92                     * but added in HTML
93                     * (ie {@link \action_plugin_combo_svg})
94                     */
95                    $requestedMime = $attributes->getMime();
96                    if ($requestedMime == TagAttributes::TEXT_HTML_MIME) {
97                        $attributes->addStyleDeclarationIfNotSet('max-width', $widthValue);
98                        $attributes->addStyleDeclarationIfNotSet('width', "100%");
99                    }
100
101                } else {
102
103                    /**
104                     * For a block
105                     */
106                    $attributes->addStyleDeclarationIfNotSet('max-width', $widthValue);
107
108                }
109            }
110
111        }
112
113        $heightName = self::HEIGHT_KEY;
114        if ($attributes->hasComponentAttribute($heightName)) {
115            $heightValue = trim($attributes->getValueAndRemove($heightName));
116            if ($heightValue !== "") {
117                $heightValue = TagAttributes::toQualifiedCssValue($heightValue);
118
119                if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) {
120
121                    /**
122                     * A element with a natural height is responsive, we set only the max-height
123                     *
124                     * By default, the image has a `height: auto` due to the img-fluid class
125                     * Making its height responsive
126                     */
127                    $attributes->addStyleDeclarationIfNotSet("max-height", $heightValue);
128
129                } else {
130
131                    /**
132                     * HTML Block
133                     *
134                     * Without the height value, a block display will collapse
135                     */
136                    if (self::HEIGHT_LAYOUT_DEFAULT == self::DESIGN_LAYOUT_CONSTRAINED) {
137
138                        /**
139                         * The box is constrained in height
140                         * By default, a box is not constrained
141                         */
142                        $attributes->addStyleDeclarationIfNotSet("height", $heightValue);
143
144                        $scrollMechanism = $attributes->getValueAndRemoveIfPresent("scroll");
145                        if ($scrollMechanism != null) {
146                            $scrollMechanism = trim(strtolower($scrollMechanism));
147                        }
148                        switch ($scrollMechanism) {
149                            case "toggle":
150                                // https://jsfiddle.net/gerardnico/h0g6xw58/
151                                $attributes->addStyleDeclarationIfNotSet("overflow-y", "hidden");
152                                $attributes->addStyleDeclarationIfNotSet("position", "relative");
153                                $attributes->addStyleDeclarationIfNotSet("display", "block");
154                                // The block should collapse to this height
155                                $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
156                                if ($attributes->hasComponentAttribute("id")) {
157                                    $id = $attributes->getValue("id");
158                                } else {
159                                    $id = $attributes->generateAndSetId();
160                                }
161                                /**
162                                 * Css of the button and other standard attribute
163                                 */
164                                PluginUtility::getSnippetManager()->attachCssSnippetForBar("height-toggle");
165                                /**
166                                 * Set the color dynamically to the color of the parent
167                                 */
168                                PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("height-toggle");
169                                /**
170                                 * The height when there is not the show class
171                                 * is the original height
172                                 */
173                                $css = <<<EOF
174#$id:not(.show){
175  height: $heightValue;
176  transition: height .35s ease;
177}
178EOF;
179                                PluginUtility::getSnippetManager()->attachCssSnippetForBar("height-toggle-show", $css);
180                                $bootstrapDataNameSpace = Bootstrap::getDataNamespace();
181                                $button = <<<EOF
182<button class="height-toggle-combo" data$bootstrapDataNameSpace-toggle="collapse" data$bootstrapDataNameSpace-target="#$id" aria-expanded="false"></button>
183EOF;
184
185                                $attributes->addHtmlAfterEnterTag($button);
186
187                                break;
188                            case "lift";
189                            default:
190                                $attributes->addStyleDeclarationIfNotSet("overflow", "auto");
191                                break;
192
193                        }
194
195
196                    } else {
197
198                        /**
199                         * if fluid
200                         * min-height and not height to not constraint the box
201                         */
202                        $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
203
204                    }
205                }
206            }
207
208        }
209    }
210
211    /**
212     *
213     * Toggle with a click on the collapsed element
214     * if there is no control element such as button or link inside
215     *
216     * This function is used at the {@link DOKU_LEXER_EXIT} state of a {@link SyntaxPlugin::handle()}
217     *
218     * @param CallStack $callStack
219     */
220    public static function addScrollToggleOnClickIfNoControl(CallStack $callStack)
221    {
222        $callStack->moveToEnd();
223        $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall();
224        $scrollAttribute = $openingCall->getAttribute(Dimension::SCROLL);
225        if ($scrollAttribute != null && $scrollAttribute == "toggle") {
226
227            $controlFound = false;
228            while ($actualCall = $callStack->next()) {
229                if (in_array($actualCall->getTagName(),
230                    [syntax_plugin_combo_button::TAG, syntax_plugin_combo_link::TAG, "internallink", "externallink"])) {
231                    $controlFound = true;
232                    break;
233                }
234            }
235            if (!$controlFound) {
236                $toggleOnClickId = "height-toggle-onclick";
237                PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar($toggleOnClickId);
238                $openingCall->addClassName("{$toggleOnClickId}-combo");
239                $openingCall->addCssStyle("cursor", "pointer");
240            }
241
242        }
243    }
244
245    /**
246     * @param $value - a css value to a pixel
247     * @throws ExceptionCombo
248     */
249    public static function toPixelValue($value): int
250    {
251        $targetValue = str_replace("px", "", $value);
252        return DataType::toInteger($targetValue);
253    }
254
255    /**
256     * Convert 16:9, ... to a float
257     * @param string $stringRatio
258     * @return float
259     * @throws ExceptionCombo
260     */
261    public static function convertTextualRatioToNumber(string $stringRatio): float
262    {
263        list($width, $height) = explode(":", $stringRatio, 2);
264        try {
265            $width = DataType::toInteger($width);
266        } catch (ExceptionCombo $e) {
267            throw new ExceptionCombo("The width value ($width) of the ratio `$stringRatio` is not numeric", syntax_plugin_combo_pageimage::CANONICAL);
268        }
269        try {
270            $height = DataType::toInteger($height);
271        } catch (ExceptionCombo $e) {
272            throw new ExceptionCombo("The width value ($height) of the ratio `$stringRatio` is not numeric", syntax_plugin_combo_pageimage::CANONICAL);
273        }
274        if ($height == 0) {
275            throw new ExceptionCombo("The height value of the ratio `$stringRatio` should not be zero", syntax_plugin_combo_pageimage::CANONICAL);
276        }
277        return floatval($width / $height);
278
279    }
280
281
282}
283