xref: /template/strap/ComboStrap/Dimension.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
137748cd8SNickeau<?php
237748cd8SNickeau
337748cd8SNickeau
437748cd8SNickeaunamespace ComboStrap;
537748cd8SNickeau
637748cd8SNickeau
7*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
837748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin;
9*04fd306cSNickeauuse syntax_plugin_combo_xmlinlinetag;
1037748cd8SNickeauuse syntax_plugin_combo_link;
1137748cd8SNickeau
1237748cd8SNickeauclass Dimension
1337748cd8SNickeau{
1437748cd8SNickeau    /**
1537748cd8SNickeau     * The element that have an width and height
1637748cd8SNickeau     */
1737748cd8SNickeau    const NATURAL_SIZING_ELEMENT = [SvgImageLink::CANONICAL, RasterImageLink::CANONICAL];
1837748cd8SNickeau
1937748cd8SNickeau    const DESIGN_LAYOUT_CONSTRAINED = "constrained"; // fix value
2037748cd8SNickeau    const DESIGN_LAYOUT_FLUID = "fluid"; // adapt
2137748cd8SNickeau
2237748cd8SNickeau    /**
2337748cd8SNickeau     * On the width, if set, the design is fluid and will adapt to all screen
2437748cd8SNickeau     * with a min-width
2537748cd8SNickeau     */
2637748cd8SNickeau    const WIDTH_LAYOUT_DEFAULT = self::DESIGN_LAYOUT_FLUID;
2737748cd8SNickeau    /**
2837748cd8SNickeau     * On height, if set, the design is constrained and overflow
2937748cd8SNickeau     */
3037748cd8SNickeau    const HEIGHT_LAYOUT_DEFAULT = self::DESIGN_LAYOUT_CONSTRAINED;
3137748cd8SNickeau    const SCROLL = "scroll";
32*04fd306cSNickeau    const SCROLL_TOGGLE_MECHANISM = "toggle";
33*04fd306cSNickeau    const SCROLL_LIFT_MECHANISM = "lift";
3482a60d03SNickeau
3582a60d03SNickeau    /**
3682a60d03SNickeau     * Logical height and width
3782a60d03SNickeau     * used by default to define the width and height of an image or a css box
3882a60d03SNickeau     */
3937748cd8SNickeau    const HEIGHT_KEY = 'height';
4037748cd8SNickeau    const WIDTH_KEY = 'width';
4137748cd8SNickeau
4282a60d03SNickeau    /**
4382a60d03SNickeau     * The ratio (16:9, ...) permits to change:
4482a60d03SNickeau     *   * the viewBox in svg
4582a60d03SNickeau     *   * the intrinsic dimension in raster
4682a60d03SNickeau     *
4782a60d03SNickeau     * It's then part of the request
4882a60d03SNickeau     * because in svg it is the definition of the viewBox
4982a60d03SNickeau     *
5082a60d03SNickeau     * The rendering function takes care of it
5182a60d03SNickeau     * and it's also passed in the fetch url
5282a60d03SNickeau     */
5382a60d03SNickeau    public const RATIO_ATTRIBUTE = "ratio";
544cadd4f8SNickeau    const ZOOM_ATTRIBUTE = "zoom";
5582a60d03SNickeau
56*04fd306cSNickeau    const CANONICAL = "dimension";
57*04fd306cSNickeau    public const WIDTH_KEY_SHORT = "w";
58*04fd306cSNickeau    public const HEIGHT_KEY_SHORT = "h";
59*04fd306cSNickeau
6037748cd8SNickeau
6137748cd8SNickeau    /**
6237748cd8SNickeau     * @param TagAttributes $attributes
6337748cd8SNickeau     */
644cadd4f8SNickeau    public static function processWidthAndHeight(TagAttributes &$attributes)
6537748cd8SNickeau    {
66*04fd306cSNickeau        self::processWidth($attributes);
6737748cd8SNickeau
6837748cd8SNickeau        $heightName = self::HEIGHT_KEY;
6937748cd8SNickeau        if ($attributes->hasComponentAttribute($heightName)) {
7037748cd8SNickeau            $heightValue = trim($attributes->getValueAndRemove($heightName));
7137748cd8SNickeau            if ($heightValue !== "") {
7237748cd8SNickeau                $heightValue = TagAttributes::toQualifiedCssValue($heightValue);
7337748cd8SNickeau
7437748cd8SNickeau                if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) {
7537748cd8SNickeau
7637748cd8SNickeau                    /**
7737748cd8SNickeau                     * A element with a natural height is responsive, we set only the max-height
7837748cd8SNickeau                     *
7937748cd8SNickeau                     * By default, the image has a `height: auto` due to the img-fluid class
8037748cd8SNickeau                     * Making its height responsive
8137748cd8SNickeau                     */
8282a60d03SNickeau                    $attributes->addStyleDeclarationIfNotSet("max-height", $heightValue);
8337748cd8SNickeau
8437748cd8SNickeau                } else {
8537748cd8SNickeau
8637748cd8SNickeau                    /**
8737748cd8SNickeau                     * HTML Block
8837748cd8SNickeau                     *
8937748cd8SNickeau                     * Without the height value, a block display will collapse
9037748cd8SNickeau                     */
9137748cd8SNickeau                    if (self::HEIGHT_LAYOUT_DEFAULT == self::DESIGN_LAYOUT_CONSTRAINED) {
9237748cd8SNickeau
9337748cd8SNickeau                        /**
9437748cd8SNickeau                         * The box is constrained in height
9537748cd8SNickeau                         * By default, a box is not constrained
9637748cd8SNickeau                         */
9782a60d03SNickeau                        $attributes->addStyleDeclarationIfNotSet("height", $heightValue);
9837748cd8SNickeau
99*04fd306cSNickeau                        $scrollMechanism = $attributes->getValueAndRemoveIfPresent(Dimension::SCROLL);
100*04fd306cSNickeau                        if ($scrollMechanism !== null) {
10137748cd8SNickeau                            $scrollMechanism = trim(strtolower($scrollMechanism));
10237748cd8SNickeau                        }
10337748cd8SNickeau                        switch ($scrollMechanism) {
104*04fd306cSNickeau                            case self::SCROLL_TOGGLE_MECHANISM:
10537748cd8SNickeau                                // https://jsfiddle.net/gerardnico/h0g6xw58/
10682a60d03SNickeau                                $attributes->addStyleDeclarationIfNotSet("overflow-y", "hidden");
10782a60d03SNickeau                                $attributes->addStyleDeclarationIfNotSet("position", "relative");
10882a60d03SNickeau                                $attributes->addStyleDeclarationIfNotSet("display", "block");
10937748cd8SNickeau                                // The block should collapse to this height
11082a60d03SNickeau                                $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
11137748cd8SNickeau                                if ($attributes->hasComponentAttribute("id")) {
11237748cd8SNickeau                                    $id = $attributes->getValue("id");
11337748cd8SNickeau                                } else {
11437748cd8SNickeau                                    $id = $attributes->generateAndSetId();
11537748cd8SNickeau                                }
11637748cd8SNickeau                                /**
11737748cd8SNickeau                                 * Css of the button and other standard attribute
11837748cd8SNickeau                                 */
119*04fd306cSNickeau                                PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("height-toggle");
12037748cd8SNickeau                                /**
12137748cd8SNickeau                                 * Set the color dynamically to the color of the parent
12237748cd8SNickeau                                 */
123*04fd306cSNickeau                                PluginUtility::getSnippetManager()->attachJavascriptFromComponentId("height-toggle");
124*04fd306cSNickeau
125*04fd306cSNickeau                                $toggleOnClickId = "height-toggle-onclick";
126*04fd306cSNickeau                                $attributes->addClassName(StyleAttribute::addComboStrapSuffix($toggleOnClickId));
127*04fd306cSNickeau                                $attributes->addStyleDeclarationIfNotSet("cursor", "pointer");
128*04fd306cSNickeau                                PluginUtility::getSnippetManager()->attachJavascriptFromComponentId($toggleOnClickId);
129*04fd306cSNickeau
13037748cd8SNickeau                                /**
13137748cd8SNickeau                                 * The height when there is not the show class
13237748cd8SNickeau                                 * is the original height
13337748cd8SNickeau                                 */
13437748cd8SNickeau                                $css = <<<EOF
13537748cd8SNickeau#$id:not(.show){
13637748cd8SNickeau  height: $heightValue;
13737748cd8SNickeau  transition: height .35s ease;
13837748cd8SNickeau}
13937748cd8SNickeauEOF;
140*04fd306cSNickeau                                PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("height-toggle-show", $css);
14137748cd8SNickeau                                $bootstrapDataNameSpace = Bootstrap::getDataNamespace();
142*04fd306cSNickeau                                $buttonClass = StyleAttribute::addComboStrapSuffix("height-toggle");
143*04fd306cSNickeau                                /** @noinspection HtmlUnknownAttribute */
14437748cd8SNickeau                                $button = <<<EOF
145*04fd306cSNickeau<button class="$buttonClass" data$bootstrapDataNameSpace-toggle="collapse" data$bootstrapDataNameSpace-target="#$id" aria-expanded="false"></button>
14637748cd8SNickeauEOF;
14737748cd8SNickeau
14837748cd8SNickeau                                $attributes->addHtmlAfterEnterTag($button);
14937748cd8SNickeau
15037748cd8SNickeau                                break;
151*04fd306cSNickeau                            case self::SCROLL_LIFT_MECHANISM;
15237748cd8SNickeau                            default:
15382a60d03SNickeau                                $attributes->addStyleDeclarationIfNotSet("overflow", "auto");
15437748cd8SNickeau                                break;
15537748cd8SNickeau
15637748cd8SNickeau                        }
15737748cd8SNickeau
15837748cd8SNickeau
15937748cd8SNickeau                    } else {
16037748cd8SNickeau
16137748cd8SNickeau                        /**
16237748cd8SNickeau                         * if fluid
16337748cd8SNickeau                         * min-height and not height to not constraint the box
16437748cd8SNickeau                         */
16582a60d03SNickeau                        $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue);
16637748cd8SNickeau
16737748cd8SNickeau                    }
16837748cd8SNickeau                }
16937748cd8SNickeau            }
17037748cd8SNickeau
17137748cd8SNickeau        }
17237748cd8SNickeau    }
17337748cd8SNickeau
17437748cd8SNickeau    /**
17537748cd8SNickeau     *
17682a60d03SNickeau     * Toggle with a click on the collapsed element
17737748cd8SNickeau     * if there is no control element such as button or link inside
17837748cd8SNickeau     *
17937748cd8SNickeau     * This function is used at the {@link DOKU_LEXER_EXIT} state of a {@link SyntaxPlugin::handle()}
18037748cd8SNickeau     *
18137748cd8SNickeau     * @param CallStack $callStack
18237748cd8SNickeau     */
18337748cd8SNickeau    public static function addScrollToggleOnClickIfNoControl(CallStack $callStack)
18437748cd8SNickeau    {
18537748cd8SNickeau        $callStack->moveToEnd();
18637748cd8SNickeau        $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall();
18737748cd8SNickeau        $scrollAttribute = $openingCall->getAttribute(Dimension::SCROLL);
188*04fd306cSNickeau        if ($scrollAttribute !== self::SCROLL_TOGGLE_MECHANISM) {
189*04fd306cSNickeau            return;
190*04fd306cSNickeau        }
19137748cd8SNickeau        while ($actualCall = $callStack->next()) {
19237748cd8SNickeau            if (in_array($actualCall->getTagName(),
193*04fd306cSNickeau                [ButtonTag::MARKUP_LONG, syntax_plugin_combo_link::TAG, "internallink", "externallink"])) {
194*04fd306cSNickeau                $openingCall->setAttribute(Dimension::SCROLL, Dimension::SCROLL_LIFT_MECHANISM);
195*04fd306cSNickeau                return;
19637748cd8SNickeau            }
19737748cd8SNickeau        }
19837748cd8SNickeau
19937748cd8SNickeau    }
20082a60d03SNickeau
20182a60d03SNickeau    /**
20282a60d03SNickeau     * @param $value - a css value to a pixel
203*04fd306cSNickeau     * @throws ExceptionCompile
204*04fd306cSNickeau     * @deprecated for {@link ConditionalLength::toPixelNumber()}
20582a60d03SNickeau     */
20682a60d03SNickeau    public static function toPixelValue($value): int
20782a60d03SNickeau    {
2084cadd4f8SNickeau
209*04fd306cSNickeau        return ConditionalLength::createFromString($value)->toPixelNumber();
210*04fd306cSNickeau
21182a60d03SNickeau    }
21282a60d03SNickeau
21382a60d03SNickeau    /**
21482a60d03SNickeau     * Convert 16:9, ... to a float
21582a60d03SNickeau     * @param string $stringRatio
21682a60d03SNickeau     * @return float
217*04fd306cSNickeau     * @throws ExceptionBadSyntax
21882a60d03SNickeau     */
21982a60d03SNickeau    public static function convertTextualRatioToNumber(string $stringRatio): float
22082a60d03SNickeau    {
22182a60d03SNickeau        list($width, $height) = explode(":", $stringRatio, 2);
22282a60d03SNickeau        try {
22382a60d03SNickeau            $width = DataType::toInteger($width);
224*04fd306cSNickeau        } catch (ExceptionCompile $e) {
225*04fd306cSNickeau            throw new ExceptionBadSyntax("The width value ($width) of the ratio `$stringRatio` is not numeric", PageImageTag::CANONICAL);
22682a60d03SNickeau        }
22782a60d03SNickeau        try {
22882a60d03SNickeau            $height = DataType::toInteger($height);
229*04fd306cSNickeau        } catch (ExceptionCompile $e) {
230*04fd306cSNickeau            throw new ExceptionBadSyntax("The width value ($height) of the ratio `$stringRatio` is not numeric", PageImageTag::CANONICAL);
23182a60d03SNickeau        }
232*04fd306cSNickeau        if ($height === 0) {
233*04fd306cSNickeau            throw new ExceptionBadSyntax("The height value of the ratio `$stringRatio` should not be zero", PageImageTag::CANONICAL);
23482a60d03SNickeau        }
23582a60d03SNickeau        return floatval($width / $height);
23682a60d03SNickeau
23782a60d03SNickeau    }
23882a60d03SNickeau
239*04fd306cSNickeau    private static function processWidth(TagAttributes $attributes)
240*04fd306cSNickeau    {
241*04fd306cSNickeau        $widthValueAsString = $attributes->getComponentAttributeValueAndRemoveIfPresent(self::WIDTH_KEY);
242*04fd306cSNickeau        if ($widthValueAsString === null) {
243*04fd306cSNickeau            return;
244*04fd306cSNickeau        }
245*04fd306cSNickeau
246*04fd306cSNickeau        $widthValueAsString = trim($widthValueAsString);
247*04fd306cSNickeau        $logicalTag = $attributes->getLogicalTag();
248*04fd306cSNickeau        if ($widthValueAsString === "") {
249*04fd306cSNickeau            LogUtility::error("The width value is empty for the tag ({$logicalTag})");
250*04fd306cSNickeau            return;
251*04fd306cSNickeau        }
252*04fd306cSNickeau        $widthValues = explode(" ", $widthValueAsString);
253*04fd306cSNickeau        foreach ($widthValues as $widthValue) {
254*04fd306cSNickeau
255*04fd306cSNickeau            try {
256*04fd306cSNickeau                $conditionalWidthLength = ConditionalLength::createFromString($widthValue);
257*04fd306cSNickeau            } catch (ExceptionBadArgument $e) {
258*04fd306cSNickeau                LogUtility::error("The width value ($widthValue) is not a valid length. Error: {$e->getMessage()}");
259*04fd306cSNickeau                continue;
260*04fd306cSNickeau            }
261*04fd306cSNickeau
262*04fd306cSNickeau
263*04fd306cSNickeau            /**
264*04fd306cSNickeau             * For an image (png, svg)
265*04fd306cSNickeau             * They have width and height **element** attribute
266*04fd306cSNickeau             */
267*04fd306cSNickeau            if (in_array($logicalTag, self::NATURAL_SIZING_ELEMENT)) {
268*04fd306cSNickeau
269*04fd306cSNickeau                /**
270*04fd306cSNickeau                 * If the image is not asked as static resource (ie HTTP request)
271*04fd306cSNickeau                 * but added in HTML
272*04fd306cSNickeau                 * (ie {@link \action_plugin_combo_svg})
273*04fd306cSNickeau                 */
274*04fd306cSNickeau                $requestedMime = $attributes->getMime();
275*04fd306cSNickeau                if ($requestedMime == TagAttributes::TEXT_HTML_MIME) {
276*04fd306cSNickeau
277*04fd306cSNickeau                    $length = $conditionalWidthLength->getLength();
278*04fd306cSNickeau                    if ($length === "0") {
279*04fd306cSNickeau
280*04fd306cSNickeau                        /**
281*04fd306cSNickeau                         * For an image, the dimension are restricted by height
282*04fd306cSNickeau                         */
283*04fd306cSNickeau                        if ($attributes->hasComponentAttribute(self::HEIGHT_KEY)) {
284*04fd306cSNickeau                            $attributes->addStyleDeclarationIfNotSet("width", "auto");
285*04fd306cSNickeau                        }
286*04fd306cSNickeau                        return;
287*04fd306cSNickeau
288*04fd306cSNickeau                    }
289*04fd306cSNickeau
290*04fd306cSNickeau                    /**
291*04fd306cSNickeau                     * For an image, the dimension are restricted by width
292*04fd306cSNickeau                     * (max-width or 100% of the container )
293*04fd306cSNickeau                     */
294*04fd306cSNickeau                    try {
295*04fd306cSNickeau                        $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->toCssLength());
296*04fd306cSNickeau                    } catch (ExceptionBadArgument $e) {
297*04fd306cSNickeau                        LogUtility::error("The conditional length ($conditionalWidthLength) could not be transformed as CSS value. Error", self::CANONICAL);
298*04fd306cSNickeau                        $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->getLength());
299*04fd306cSNickeau                    }
300*04fd306cSNickeau                    $attributes->addStyleDeclarationIfNotSet('width', "100%");
301*04fd306cSNickeau                }
302*04fd306cSNickeau                return;
303*04fd306cSNickeau            }
304*04fd306cSNickeau
305*04fd306cSNickeau            /**
306*04fd306cSNickeau             * For a element without natural sizing
307*04fd306cSNickeau             */
308*04fd306cSNickeau            $unit = $conditionalWidthLength->getLengthUnit();
309*04fd306cSNickeau            switch ($unit) {
310*04fd306cSNickeau                case ConditionalLength::PERCENTAGE:
311*04fd306cSNickeau                    try {
312*04fd306cSNickeau                        $attributes->addClassName($conditionalWidthLength->toColClass());
313*04fd306cSNickeau                    } catch (ExceptionBadArgument $e) {
314*04fd306cSNickeau                        LogUtility::error("The conditional length ($conditionalWidthLength) could not be converted to a col class. Error: {$e->getMessage()}");
315*04fd306cSNickeau                    }
316*04fd306cSNickeau                    break;
317*04fd306cSNickeau                default:
318*04fd306cSNickeau                    try {
319*04fd306cSNickeau                        $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->toCssLength());
320*04fd306cSNickeau                        // to overcome the setting 'fit-content' set by auto ...
321*04fd306cSNickeau                        $attributes->setStyleDeclaration('width', 'auto');
322*04fd306cSNickeau                    } catch (ExceptionBadArgument $e) {
323*04fd306cSNickeau                        LogUtility::error("The conditional length ($conditionalWidthLength) could not be transformed as CSS value. Error", self::CANONICAL);
324*04fd306cSNickeau                        $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->getLength());
325*04fd306cSNickeau                    }
326*04fd306cSNickeau                    break;
327*04fd306cSNickeau            }
328*04fd306cSNickeau
329*04fd306cSNickeau
330*04fd306cSNickeau        }
331*04fd306cSNickeau
332*04fd306cSNickeau    }
333*04fd306cSNickeau
33482a60d03SNickeau
33537748cd8SNickeau}
336