1<?php 2 3 4namespace ComboStrap; 5 6 7use ComboStrap\TagAttribute\StyleAttribute; 8use dokuwiki\Extension\SyntaxPlugin; 9use syntax_plugin_combo_xmlinlinetag; 10use syntax_plugin_combo_link; 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 const SCROLL_TOGGLE_MECHANISM = "toggle"; 33 const SCROLL_LIFT_MECHANISM = "lift"; 34 35 /** 36 * Logical height and width 37 * used by default to define the width and height of an image or a css box 38 */ 39 const HEIGHT_KEY = 'height'; 40 const WIDTH_KEY = 'width'; 41 42 /** 43 * The ratio (16:9, ...) permits to change: 44 * * the viewBox in svg 45 * * the intrinsic dimension in raster 46 * 47 * It's then part of the request 48 * because in svg it is the definition of the viewBox 49 * 50 * The rendering function takes care of it 51 * and it's also passed in the fetch url 52 */ 53 public const RATIO_ATTRIBUTE = "ratio"; 54 const ZOOM_ATTRIBUTE = "zoom"; 55 56 const CANONICAL = "dimension"; 57 public const WIDTH_KEY_SHORT = "w"; 58 public const HEIGHT_KEY_SHORT = "h"; 59 60 61 /** 62 * @param TagAttributes $attributes 63 */ 64 public static function processWidthAndHeight(TagAttributes &$attributes) 65 { 66 self::processWidth($attributes); 67 68 $heightName = self::HEIGHT_KEY; 69 if ($attributes->hasComponentAttribute($heightName)) { 70 $heightValue = trim($attributes->getValueAndRemove($heightName)); 71 if ($heightValue !== "") { 72 $heightValue = TagAttributes::toQualifiedCssValue($heightValue); 73 74 if (in_array($attributes->getLogicalTag(), self::NATURAL_SIZING_ELEMENT)) { 75 76 /** 77 * A element with a natural height is responsive, we set only the max-height 78 * 79 * By default, the image has a `height: auto` due to the img-fluid class 80 * Making its height responsive 81 */ 82 $attributes->addStyleDeclarationIfNotSet("max-height", $heightValue); 83 84 } else { 85 86 /** 87 * HTML Block 88 * 89 * Without the height value, a block display will collapse 90 */ 91 if (self::HEIGHT_LAYOUT_DEFAULT == self::DESIGN_LAYOUT_CONSTRAINED) { 92 93 /** 94 * The box is constrained in height 95 * By default, a box is not constrained 96 */ 97 $attributes->addStyleDeclarationIfNotSet("height", $heightValue); 98 99 $scrollMechanism = $attributes->getValueAndRemoveIfPresent(Dimension::SCROLL); 100 if ($scrollMechanism !== null) { 101 $scrollMechanism = trim(strtolower($scrollMechanism)); 102 } 103 switch ($scrollMechanism) { 104 case self::SCROLL_TOGGLE_MECHANISM: 105 // https://jsfiddle.net/gerardnico/h0g6xw58/ 106 $attributes->addStyleDeclarationIfNotSet("overflow-y", "hidden"); 107 $attributes->addStyleDeclarationIfNotSet("position", "relative"); 108 $attributes->addStyleDeclarationIfNotSet("display", "block"); 109 // The block should collapse to this height 110 $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue); 111 if ($attributes->hasComponentAttribute("id")) { 112 $id = $attributes->getValue("id"); 113 } else { 114 $id = $attributes->generateAndSetId(); 115 } 116 /** 117 * Css of the button and other standard attribute 118 */ 119 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("height-toggle"); 120 /** 121 * Set the color dynamically to the color of the parent 122 */ 123 PluginUtility::getSnippetManager()->attachJavascriptFromComponentId("height-toggle"); 124 125 $toggleOnClickId = "height-toggle-onclick"; 126 $attributes->addClassName(StyleAttribute::addComboStrapSuffix($toggleOnClickId)); 127 $attributes->addStyleDeclarationIfNotSet("cursor", "pointer"); 128 PluginUtility::getSnippetManager()->attachJavascriptFromComponentId($toggleOnClickId); 129 130 /** 131 * The height when there is not the show class 132 * is the original height 133 */ 134 $css = <<<EOF 135#$id:not(.show){ 136 height: $heightValue; 137 transition: height .35s ease; 138} 139EOF; 140 PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("height-toggle-show", $css); 141 $bootstrapDataNameSpace = Bootstrap::getDataNamespace(); 142 $buttonClass = StyleAttribute::addComboStrapSuffix("height-toggle"); 143 /** @noinspection HtmlUnknownAttribute */ 144 $button = <<<EOF 145<button class="$buttonClass" data$bootstrapDataNameSpace-toggle="collapse" data$bootstrapDataNameSpace-target="#$id" aria-expanded="false"></button> 146EOF; 147 148 $attributes->addHtmlAfterEnterTag($button); 149 150 break; 151 case self::SCROLL_LIFT_MECHANISM; 152 default: 153 $attributes->addStyleDeclarationIfNotSet("overflow", "auto"); 154 break; 155 156 } 157 158 159 } else { 160 161 /** 162 * if fluid 163 * min-height and not height to not constraint the box 164 */ 165 $attributes->addStyleDeclarationIfNotSet("min-height", $heightValue); 166 167 } 168 } 169 } 170 171 } 172 } 173 174 /** 175 * 176 * Toggle with a click on the collapsed element 177 * if there is no control element such as button or link inside 178 * 179 * This function is used at the {@link DOKU_LEXER_EXIT} state of a {@link SyntaxPlugin::handle()} 180 * 181 * @param CallStack $callStack 182 */ 183 public static function addScrollToggleOnClickIfNoControl(CallStack $callStack) 184 { 185 $callStack->moveToEnd(); 186 $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall(); 187 $scrollAttribute = $openingCall->getAttribute(Dimension::SCROLL); 188 if ($scrollAttribute !== self::SCROLL_TOGGLE_MECHANISM) { 189 return; 190 } 191 while ($actualCall = $callStack->next()) { 192 if (in_array($actualCall->getTagName(), 193 [ButtonTag::MARKUP_LONG, syntax_plugin_combo_link::TAG, "internallink", "externallink"])) { 194 $openingCall->setAttribute(Dimension::SCROLL, Dimension::SCROLL_LIFT_MECHANISM); 195 return; 196 } 197 } 198 199 } 200 201 /** 202 * @param $value - a css value to a pixel 203 * @throws ExceptionCompile 204 * @deprecated for {@link ConditionalLength::toPixelNumber()} 205 */ 206 public static function toPixelValue($value): int 207 { 208 209 return ConditionalLength::createFromString($value)->toPixelNumber(); 210 211 } 212 213 /** 214 * Convert 16:9, ... to a float 215 * @param string $stringRatio 216 * @return float 217 * @throws ExceptionBadSyntax 218 */ 219 public static function convertTextualRatioToNumber(string $stringRatio): float 220 { 221 list($width, $height) = explode(":", $stringRatio, 2); 222 try { 223 $width = DataType::toInteger($width); 224 } catch (ExceptionCompile $e) { 225 throw new ExceptionBadSyntax("The width value ($width) of the ratio `$stringRatio` is not numeric", PageImageTag::CANONICAL); 226 } 227 try { 228 $height = DataType::toInteger($height); 229 } catch (ExceptionCompile $e) { 230 throw new ExceptionBadSyntax("The width value ($height) of the ratio `$stringRatio` is not numeric", PageImageTag::CANONICAL); 231 } 232 if ($height === 0) { 233 throw new ExceptionBadSyntax("The height value of the ratio `$stringRatio` should not be zero", PageImageTag::CANONICAL); 234 } 235 return floatval($width / $height); 236 237 } 238 239 private static function processWidth(TagAttributes $attributes) 240 { 241 $widthValueAsString = $attributes->getComponentAttributeValueAndRemoveIfPresent(self::WIDTH_KEY); 242 if ($widthValueAsString === null) { 243 return; 244 } 245 246 $widthValueAsString = trim($widthValueAsString); 247 $logicalTag = $attributes->getLogicalTag(); 248 if ($widthValueAsString === "") { 249 LogUtility::error("The width value is empty for the tag ({$logicalTag})"); 250 return; 251 } 252 $widthValues = explode(" ", $widthValueAsString); 253 foreach ($widthValues as $widthValue) { 254 255 try { 256 $conditionalWidthLength = ConditionalLength::createFromString($widthValue); 257 } catch (ExceptionBadArgument $e) { 258 LogUtility::error("The width value ($widthValue) is not a valid length. Error: {$e->getMessage()}"); 259 continue; 260 } 261 262 263 /** 264 * For an image (png, svg) 265 * They have width and height **element** attribute 266 */ 267 if (in_array($logicalTag, self::NATURAL_SIZING_ELEMENT)) { 268 269 /** 270 * If the image is not asked as static resource (ie HTTP request) 271 * but added in HTML 272 * (ie {@link \action_plugin_combo_svg}) 273 */ 274 $requestedMime = $attributes->getMime(); 275 if ($requestedMime == TagAttributes::TEXT_HTML_MIME) { 276 277 $length = $conditionalWidthLength->getLength(); 278 if ($length === "0") { 279 280 /** 281 * For an image, the dimension are restricted by height 282 */ 283 if ($attributes->hasComponentAttribute(self::HEIGHT_KEY)) { 284 $attributes->addStyleDeclarationIfNotSet("width", "auto"); 285 } 286 return; 287 288 } 289 290 /** 291 * For an image, the dimension are restricted by width 292 * (max-width or 100% of the container ) 293 */ 294 try { 295 $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->toCssLength()); 296 } catch (ExceptionBadArgument $e) { 297 LogUtility::error("The conditional length ($conditionalWidthLength) could not be transformed as CSS value. Error", self::CANONICAL); 298 $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->getLength()); 299 } 300 $attributes->addStyleDeclarationIfNotSet('width', "100%"); 301 } 302 return; 303 } 304 305 /** 306 * For a element without natural sizing 307 */ 308 $unit = $conditionalWidthLength->getLengthUnit(); 309 switch ($unit) { 310 case ConditionalLength::PERCENTAGE: 311 try { 312 $attributes->addClassName($conditionalWidthLength->toColClass()); 313 } catch (ExceptionBadArgument $e) { 314 LogUtility::error("The conditional length ($conditionalWidthLength) could not be converted to a col class. Error: {$e->getMessage()}"); 315 } 316 break; 317 default: 318 try { 319 $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->toCssLength()); 320 // to overcome the setting 'fit-content' set by auto ... 321 $attributes->setStyleDeclaration('width', 'auto'); 322 } catch (ExceptionBadArgument $e) { 323 LogUtility::error("The conditional length ($conditionalWidthLength) could not be transformed as CSS value. Error", self::CANONICAL); 324 $attributes->addStyleDeclarationIfNotSet('max-width', $conditionalWidthLength->getLength()); 325 } 326 break; 327 } 328 329 330 } 331 332 } 333 334 335} 336