1<?php 2 3 4namespace ComboStrap; 5 6 7/** 8 * Process background attribute 9 */ 10class Background 11{ 12 /** 13 * Background Logical attribute / Public 14 */ 15 const BACKGROUND_COLOR = 'background-color'; 16 const BACKGROUND_FILL = "fill"; 17 18 19 /** 20 * CSS attribute / not public 21 */ 22 const BACKGROUND_IMAGE = 'background-image'; 23 const BACKGROUND_SIZE = "background-size"; 24 const BACKGROUND_REPEAT = "background-repeat"; 25 const BACKGROUND_POSITION = "background-position"; 26 27 28 /** 29 * The page canonical of the documentation 30 */ 31 const CANONICAL = "background"; 32 /** 33 * A component attributes to store backgrounds 34 */ 35 const BACKGROUNDS = "backgrounds"; 36 37 /** 38 * Pattern css 39 * https://bansal.io/pattern-css 40 */ 41 const PATTERN_ATTRIBUTE = "pattern"; 42 const PATTERN_CSS_SNIPPET_ID = "pattern.css"; 43 const PATTERN_CSS_SIZE = ["sm", "md", "lg", "xl"]; 44 const PATTERN_NAMES = ['checks', 'grid', 'dots', 'cross-dots', 'diagonal-lines', 'horizontal-lines', 'vertical-lines', 'diagonal-stripes', 'horizontal-stripes', 'vertical-stripes', 'triangles', 'zigzag']; 45 const PATTERN_CSS_CLASS_PREFIX = "pattern"; 46 const PATTERN_COLOR_ATTRIBUTE = "pattern-color"; 47 48 49 public static function processBackgroundAttributes(TagAttributes &$tagAttributes) 50 { 51 52 /** 53 * Backgrounds set with the {@link \syntax_plugin_combo_background} component 54 */ 55 if ($tagAttributes->hasComponentAttribute(self::BACKGROUNDS)) { 56 PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::CANONICAL); 57 $backgrounds = $tagAttributes->getValueAndRemove(self::BACKGROUNDS); 58 switch (sizeof($backgrounds)) { 59 case 1: 60 // Only one background was specified 61 $background = $backgrounds[0]; 62 if ( 63 /** 64 * We need to create a background node 65 * if we transform or 66 * use a CSS pattern (because it use the text color as one of painting color) 67 */ 68 !isset($background[TagAttributes::TRANSFORM]) && 69 !isset($background[self::PATTERN_ATTRIBUTE]) 70 ) { 71 /** 72 * For readability, 73 * we put the background on the parent node 74 * because there is only one background 75 */ 76 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_IMAGE, $background[self::BACKGROUND_IMAGE]); 77 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_COLOR, $background[self::BACKGROUND_COLOR]); 78 $tagAttributes->addComponentAttributeValueIfNotEmpty(Opacity::OPACITY_ATTRIBUTE, $background[Opacity::OPACITY_ATTRIBUTE]); 79 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_POSITION, $background[self::BACKGROUND_POSITION]); 80 $tagAttributes->addComponentAttributeValueIfNotEmpty(self::BACKGROUND_FILL, $background[self::BACKGROUND_FILL]); 81 } else { 82 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 83 $backgroundTagAttribute->addClassName(self::CANONICAL); 84 $backgroundHTML = "<div class=\"backgrounds\">" . DOKU_LF . 85 $backgroundTagAttribute->toHtmlEnterTag("div") . 86 "</div>" . DOKU_LF . 87 "</div>"; 88 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 89 } 90 break; 91 default: 92 /** 93 * More than one background 94 * This backgrounds should have been set on the backgrounds 95 */ 96 $backgroundHTML = ""; 97 foreach ($backgrounds as $background) { 98 $backgroundTagAttribute = TagAttributes::createFromCallStackArray($background); 99 $backgroundTagAttribute->addClassName(self::CANONICAL); 100 $backgroundHTMLEnter = $backgroundTagAttribute->toHtmlEnterTag("div"); 101 $backgroundHTML .= $backgroundHTMLEnter . "</div>"; 102 } 103 $tagAttributes->addHtmlAfterEnterTag($backgroundHTML); 104 break; 105 } 106 } 107 108 /** 109 * Background-image attribute 110 */ 111 $backgroundImageStyleValue = ""; 112 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_IMAGE)) { 113 $backgroundImageValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 114 if (is_string($backgroundImageValue)) { 115 /** 116 * Image background is set by the user 117 */ 118 $backgroundImageStyleValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_IMAGE); 119 120 } else { 121 122 if (is_array($backgroundImageValue)) { 123 124 /** 125 * Background-fill for background image 126 */ 127 $backgroundFill = $tagAttributes->getValueAndRemove(self::BACKGROUND_FILL, "cover"); 128 switch ($backgroundFill) { 129 case "cover": 130 // it makes the background responsive 131 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_SIZE, $backgroundFill); 132 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "no-repeat"); 133 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_POSITION, "center center"); 134 135 /** 136 * The type of image is important for the processing of SVG 137 */ 138 $backgroundImageValue[TagAttributes::TYPE_KEY] = SvgDocument::ILLUSTRATION_TYPE; 139 break; 140 case "tile": 141 // background size is then "auto" (ie repeat), the default 142 // background position is not needed (the tile start on the left top corner) 143 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_REPEAT, "repeat"); 144 145 /** 146 * The type of image is important for the processing of SVG 147 * A tile needs to have a width and a height 148 */ 149 $backgroundImageValue[TagAttributes::TYPE_KEY] = SvgDocument::TILE_TYPE; 150 break; 151 case "css": 152 // custom, set by the user in own css stylesheet, nothing to do 153 break; 154 default: 155 LogUtility::msg("The background `fill` attribute ($backgroundFill) is unknown. If you want to take over the filling via css, set the `fill` value to `css`.", self::CANONICAL); 156 break; 157 } 158 159 160 $media = MediaLink::createFromCallStackArray($backgroundImageValue); 161 if ($media instanceof ThirdMediaLink) { 162 LogUtility::msg("The background image ($media) is not supported", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 163 return; 164 } 165 /** 166 * @var Image $image 167 */ 168 $image = $media->getDefaultImage(); 169 if (!FileSystems::exists($image->getPath())) { 170 LogUtility::msg("The image ($media) does not exist", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 171 return; 172 } 173 $url = $image->getUrl(DokuwikiUrl::AMPERSAND_CHARACTER); 174 $backgroundImageStyleValue = "url(" . $url . ")"; 175 176 } else { 177 LogUtility::msg("Internal Error: The background image value ($backgroundImageValue) is not a string nor an array", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 178 } 179 180 } 181 } 182 if (!empty($backgroundImageStyleValue)) { 183 if ($tagAttributes->hasComponentAttribute(Opacity::OPACITY_ATTRIBUTE)) { 184 $opacity = $tagAttributes->getValueAndRemove(Opacity::OPACITY_ATTRIBUTE); 185 $finalOpacity = 1 - $opacity; 186 /** 187 * In the argument of linear-gradient, we don't use `0 100%` to apply the 188 * same image everywhere 189 * 190 * because the validator https://validator.w3.org/ would complain with 191 * ` 192 * CSS: background-image: too few values for the property linear-gradient. 193 * ` 194 */ 195 $backgroundImageStyleValue = "linear-gradient(to right, rgba(255,255,255, $finalOpacity) 0 50%, rgba(255,255,255, $finalOpacity) 50% 100%)," . $backgroundImageStyleValue; 196 } 197 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, $backgroundImageStyleValue); 198 } 199 200 201 /** 202 * Process the pattern css 203 * https://bansal.io/pattern-css 204 * This call should be before the processing of the background color 205 * because it will set one if it's not available 206 */ 207 self::processPatternAttribute($tagAttributes); 208 209 /** 210 * Background color 211 */ 212 if ($tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 213 214 $colorValue = $tagAttributes->getValueAndRemove(self::BACKGROUND_COLOR); 215 216 $gradientPrefix = 'gradient-'; 217 if (strpos($colorValue, $gradientPrefix) === 0) { 218 /** 219 * A gradient is an image 220 * Check that there is no image 221 */ 222 if (!empty($backgroundImageStyleValue)) { 223 LogUtility::msg("An image and a linear gradient color are exclusive because a linear gradient color creates an image. You can't use the linear color (" . $colorValue . ") and the image (" . $backgroundImageStyleValue . ")", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 224 } else { 225 $mainColorValue = substr($colorValue, strlen($gradientPrefix)); 226 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_IMAGE, 'linear-gradient(to top,#fff 0,' . ColorRgb::createFromString($mainColorValue)->toCssValue() . ' 100%)'); 227 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, 'unset!important'); 228 } 229 } else { 230 231 $colorValue = ColorRgb::createFromString($colorValue)->toCssValue(); 232 $tagAttributes->addStyleDeclarationIfNotSet(self::BACKGROUND_COLOR, $colorValue); 233 234 } 235 } 236 237 238 } 239 240 /** 241 * Return a background array with background properties 242 * from a media {@link MediaLink::toCallStackArray()} 243 * @param array $mediaCallStackArray 244 * @return array 245 */ 246 public static function fromMediaToBackgroundImageStackArray(array $mediaCallStackArray): array 247 { 248 $backgroundProperties = []; 249 foreach ($mediaCallStackArray as $key => $property) { 250 switch ($key) { 251 case MediaLink::LINKING_KEY: 252 case TagAttributes::TITLE_KEY: 253 case MediaLink::ALIGN_KEY: 254 case FloatAttribute::FLOAT_KEY: // Float is when the image is at the right 255 /** 256 * Attributes not taken 257 */ 258 break; 259 case TagAttributes::TYPE_KEY: // needed for the metadata registration 260 default: 261 /** 262 * Attributes taken 263 */ 264 $backgroundProperties[$key] = $property; 265 break; 266 } 267 } 268 return $backgroundProperties; 269 } 270 271 /** 272 * @param TagAttributes $tagAttributes 273 * Process the `pattern` attribute 274 * that implements 275 * https://bansal.io/pattern-css 276 */ 277 private static function processPatternAttribute(TagAttributes &$tagAttributes) 278 { 279 /** 280 * Css Pattern 281 */ 282 if ($tagAttributes->hasComponentAttribute(self::PATTERN_ATTRIBUTE)) { 283 284 /** 285 * Attach the stylesheet 286 */ 287 PluginUtility::getSnippetManager()->attachCssExternalStyleSheetForSlot( 288 self::PATTERN_CSS_SNIPPET_ID, 289 "https://cdn.jsdelivr.net/npm/pattern.css@1.0.0/dist/pattern.min.css", 290 "sha256-Vwich3JPJa27TO9g6q+TxJGE7DNEigBaHNPm+KkMR6o=") 291 ->setCritical(false); // not blocking for rendering 292 293 294 $patternValue = strtolower($tagAttributes->getValueAndRemove(self::PATTERN_ATTRIBUTE)); 295 296 $lastIndexOfMinus = StringUtility::lastIndexOf($patternValue, "-"); 297 $lastMinusPart = substr($patternValue, $lastIndexOfMinus); 298 /** 299 * Do we have the size as last part 300 */ 301 if (!in_array($lastMinusPart, self::PATTERN_CSS_SIZE)) { 302 /** 303 * We don't have a size 304 */ 305 $pattern = $patternValue; 306 $size = "md"; 307 } else { 308 $pattern = substr($patternValue, 0, $lastIndexOfMinus); 309 $size = $lastMinusPart; 310 } 311 /** 312 * Does this pattern is a known pattern 313 */ 314 if (!in_array($pattern, self::PATTERN_NAMES)) { 315 LogUtility::msg("The pattern (" . $pattern . ") is not a known CSS pattern and was ignored.", LogUtility::LVL_MSG_WARNING, self::CANONICAL); 316 return; 317 } else { 318 $tagAttributes->addClassName(self::PATTERN_CSS_CLASS_PREFIX . "-" . $pattern . "-" . $size); 319 } 320 321 if (!$tagAttributes->hasComponentAttribute(self::BACKGROUND_COLOR)) { 322 LogUtility::msg("The background color was not set for the background with the (" . $pattern . "). It was set to the default color.", LogUtility::LVL_MSG_INFO, self::CANONICAL); 323 $tagAttributes->addComponentAttributeValue(self::BACKGROUND_COLOR, "steelblue"); 324 } 325 /** 326 * Color 327 */ 328 if ($tagAttributes->hasComponentAttribute(self::PATTERN_COLOR_ATTRIBUTE)) { 329 $patternColor = $tagAttributes->getValueAndRemove(self::PATTERN_COLOR_ATTRIBUTE); 330 } else { 331 LogUtility::msg("The pattern color was not set for the background with the (" . $pattern . "). It was set to the default color.", LogUtility::LVL_MSG_INFO, self::CANONICAL); 332 $patternColor = "#FDE482"; 333 } 334 $tagAttributes->addStyleDeclarationIfNotSet(ColorRgb::COLOR, $patternColor); 335 336 } 337 } 338 339 340} 341