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