1<?php 2/** 3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4 * 5 * This source code is licensed under the GPL license found in the 6 * COPYING file in the root directory of this source tree. 7 * 8 * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9 * @author ComboStrap <support@combostrap.com> 10 * 11 */ 12 13namespace ComboStrap; 14 15require_once(__DIR__ . '/PluginUtility.php'); 16 17 18/** 19 * Class IconUtility 20 * @package ComboStrap 21 * @see https://combostrap.com/icon 22 * 23 * 24 * Material design does not have a repository structure where we can extract the location 25 * from the name 26 * https://material.io/resources/icons https://google.github.io/material-design-icons/ 27 * 28 * Injection via javascript to avoid problem with the php svgsimple library 29 * https://www.npmjs.com/package/svg-injector 30 */ 31class Icon extends ImageSvg 32{ 33 const CONF_ICONS_MEDIA_NAMESPACE = "icons_namespace"; 34 const CONF_ICONS_MEDIA_NAMESPACE_DEFAULT = ":" . PluginUtility::COMBOSTRAP_NAMESPACE_NAME . ":icons"; 35 // Canonical name 36 const ICON_CANONICAL_NAME = "icon"; 37 38 39 const ICON_LIBRARY_URLS = array( 40 self::ANT_DESIGN => "https://raw.githubusercontent.com/ant-design/ant-design-icons/master/packages/icons-svg/svg", 41 self::BOOTSTRAP => "https://raw.githubusercontent.com/twbs/icons/main/icons", 42 self::CARBON => "https://raw.githubusercontent.com/carbon-design-system/carbon/main/packages/icons/src/svg/32", 43 self::CLARITY => "https://raw.githubusercontent.com/vmware/clarity-assets/master/icons/essential", 44 self::CODE_ICON => "https://raw.githubusercontent.com/microsoft/vscode-codicons/main/src/icons", 45 self::ELEGANT_THEME => "https://raw.githubusercontent.com/pprince/etlinefont-bower/master/images/svg/individual_icons", 46 self::ENTYPO => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo", 47 self::ENTYPO_SOCIAL => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo%20Social%20Extension", 48 self::EVA => "https://raw.githubusercontent.com/akveo/eva-icons/master/package/icons", 49 self::FEATHER => "https://raw.githubusercontent.com/feathericons/feather/master/icons", 50 self::FAD => "https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs", 51 self::ICONSCOUT => "https://raw.githubusercontent.com/Iconscout/unicons/master/svg/line", 52 self::LOGOS => "https://raw.githubusercontent.com/gilbarbara/logos/master/logos", 53 self::MATERIAL_DESIGN => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg", 54 self::OCTICON => "https://raw.githubusercontent.com/primer/octicons/main/icons", 55 self::TWEET_EMOJI => "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg", 56 self::SIMPLE_LINE => "https://raw.githubusercontent.com/thesabbir/simple-line-icons/master/src/svgs", 57 self::ICOMOON => "https://raw.githubusercontent.com/Keyamoon/IcoMoon-Free/master/SVG", 58 self::DASHICONS => "https://raw.githubusercontent.com/WordPress/dashicons/master/svg-min", 59 self::ICONOIR => "https://raw.githubusercontent.com/lucaburgio/iconoir/master/icons", 60 self::BOX_ICON => "https://raw.githubusercontent.com/atisawd/boxicons/master/svg", 61 self::LINE_AWESOME => "https://raw.githubusercontent.com/icons8/line-awesome/master/svg", 62 self::FONT_AWESOME_SOLID => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/solid", 63 self::FONT_AWESOME_BRANDS => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/brands", 64 self::FONT_AWESOME_REGULAR => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/regular", 65 self::VAADIN => "https://raw.githubusercontent.com/vaadin/vaadin-icons/master/assets/svg", 66 self::CORE_UI_BRAND => "https://raw.githubusercontent.com/coreui/coreui-icons/master/svg/brand", 67 self::FLAT_COLOR_ICON => "https://raw.githubusercontent.com/icons8/flat-color-icons/master/svg", 68 self::PHOSPHOR_ICONS => "https://raw.githubusercontent.com/phosphor-icons/phosphor-icons/master/assets", 69 self::VSCODE => "https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons", 70 self::SI_GLYPH => "https://raw.githubusercontent.com/frexy/glyph-iconset/master/svg", 71 self::AKAR_ICONS => "https://raw.githubusercontent.com/artcoholic/akar-icons/master/src/svg" 72 ); 73 74 const ICON_LIBRARY_WEBSITE_URLS = array( 75 self::BOOTSTRAP => "https://icons.getbootstrap.com/", 76 self::MATERIAL_DESIGN => "https://materialdesignicons.com/", 77 self::FEATHER => "https://feathericons.com/", 78 self::CODE_ICON => "https://microsoft.github.io/vscode-codicons/", 79 self::LOGOS => "https://svgporn.com/", 80 self::CARBON => "https://www.carbondesignsystem.com/guidelines/icons/library/", 81 self::TWEET_EMOJI => "https://twemoji.twitter.com/", 82 self::ANT_DESIGN => "https://ant.design/components/icon/", 83 self::CLARITY => "https://clarity.design/foundation/icons/", 84 self::OCTICON => "https://primer.style/octicons/", 85 self::ICONSCOUT => "https://iconscout.com/unicons/explore/line", 86 self::ELEGANT_THEME => "https://github.com/pprince/etlinefont-bower", 87 self::EVA => "https://akveo.github.io/eva-icons/", 88 self::ENTYPO_SOCIAL => "http://www.entypo.com", 89 self::ENTYPO => "http://www.entypo.com", 90 self::SIMPLE_LINE => "https://thesabbir.github.io/simple-line-icons", 91 self::ICOMOON => "https://icomoon.io/", 92 self::DASHICONS => "https://developer.wordpress.org/resource/dashicons/", 93 self::ICONOIR => "https://iconoir.com", 94 self::BOX_ICON => "https://boxicons.com", 95 self::LINE_AWESOME => "https://icons8.com/line-awesome", 96 self::FONT_AWESOME => "https://fontawesome.com/", 97 self::FONT_AWESOME_SOLID => "https://fontawesome.com/", 98 self::FONT_AWESOME_BRANDS => "https://fontawesome.com/", 99 self::FONT_AWESOME_REGULAR => "https://fontawesome.com/", 100 self::VAADIN => "https://vaadin.com/icons", 101 self::CORE_UI_BRAND => "https://coreui.io/icons/", 102 self::FLAT_COLOR_ICON => "https://icons8.com/icons/color", 103 self::PHOSPHOR_ICONS => "https://phosphoricons.com/", 104 self::VSCODE => "https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons", 105 self::SI_GLYPH => "https://glyph.smarticons.co/", 106 self::AKAR_ICONS => "https://akaricons.com/" 107 108 ); 109 110 const CONF_DEFAULT_ICON_LIBRARY = "defaultIconLibrary"; 111 const CONF_DEFAULT_ICON_LIBRARY_DEFAULT = self::MATERIAL_DESIGN_ACRONYM; 112 113 /** 114 * Deprecated library acronym / name 115 */ 116 const DEPRECATED_LIBRARY_ACRONYM = array( 117 "bs" => self::BOOTSTRAP, // old one (deprecated) - the good acronym is bi (seen also in the class) 118 "md" => self::MATERIAL_DESIGN 119 ); 120 121 /** 122 * Public known acronym / name (Used in the configuration) 123 */ 124 const PUBLIC_LIBRARY_ACRONYM = array( 125 "bi" => self::BOOTSTRAP, 126 self::MATERIAL_DESIGN_ACRONYM => self::MATERIAL_DESIGN, 127 "fe" => self::FEATHER, 128 "codicon" => self::CODE_ICON, 129 "logos" => self::LOGOS, 130 "carbon" => self::CARBON, 131 "twemoji" => self::TWEET_EMOJI, 132 "ant-design" => self::ANT_DESIGN, 133 "fad" => self::FAD, 134 "clarity" => self::CLARITY, 135 "octicon" => self::OCTICON, 136 "uit" => self::ICONSCOUT, 137 "et" => self::ELEGANT_THEME, 138 "eva" => self::EVA, 139 "entypo-social" => self::ENTYPO_SOCIAL, 140 "entypo" => self::ENTYPO, 141 "simple-line-icons" => self::SIMPLE_LINE, 142 "icomoon-free" => self::ICOMOON, 143 "dashicons" => self::DASHICONS, 144 "iconoir" => self::ICONOIR, 145 "bx" => self::BOX_ICON, 146 "la" => self::LINE_AWESOME, 147 "fa-solid" => self::FONT_AWESOME_SOLID, 148 "fa-brands" => self::FONT_AWESOME_BRANDS, 149 "fa-regular" => self::FONT_AWESOME_REGULAR, 150 "vaadin" => self::VAADIN, 151 "cib" => self::CORE_UI_BRAND, 152 "flat-color-icons" => self::FLAT_COLOR_ICON, 153 "ph" => self::PHOSPHOR_ICONS, 154 "vscode-icons" => self::VSCODE, 155 "si-glyph" => self::SI_GLYPH, 156 "akar-icons" => self::AKAR_ICONS 157 ); 158 159 const FEATHER = "feather"; 160 const BOOTSTRAP = "bootstrap"; 161 const MATERIAL_DESIGN = "material-design"; 162 const CODE_ICON = "codicon"; 163 const LOGOS = "logos"; 164 const CARBON = "carbon"; 165 const MATERIAL_DESIGN_ACRONYM = "mdi"; 166 const TWEET_EMOJI = "twemoji"; 167 const ANT_DESIGN = "ant-design"; 168 const FAD = "fad"; 169 const CLARITY = "clarity"; 170 const OCTICON = "octicon"; 171 const ICONSCOUT = "iconscout"; 172 const ELEGANT_THEME = "elegant-theme"; 173 const EVA = "eva"; 174 const ENTYPO_SOCIAL = "entypo-social"; 175 const ENTYPO = "entypo"; 176 const SIMPLE_LINE = "simple-line"; 177 const ICOMOON = "icomoon"; 178 const DASHICONS = " dashicons"; 179 const ICONOIR = "iconoir"; 180 const BOX_ICON = "box-icon"; 181 const LINE_AWESOME = "line-awesome"; 182 const FONT_AWESOME_SOLID = "font-awesome-solid"; 183 const FONT_AWESOME_BRANDS = "font-awesome-brands"; 184 const FONT_AWESOME_REGULAR = "font-awesome-regular"; 185 const FONT_AWESOME = "font-awesome"; 186 const VAADIN = "vaadin"; 187 const CORE_UI_BRAND = "cib"; 188 const FLAT_COLOR_ICON = "flat-color-icons"; 189 const PHOSPHOR_ICONS = "ph"; 190 const VSCODE = "vscode"; 191 const SI_GLYPH = "si-glyph"; 192 const COMBO = DokuPath::COMBO_DRIVE; 193 const AKAR_ICONS = "akar-icons"; 194 195 196 private $fullQualifiedName; 197 /** 198 * The icon library 199 * @var mixed|null 200 */ 201 private $library; 202 /** 203 * @var false|string 204 */ 205 private $iconName; 206 207 /** 208 * Icon constructor. 209 * @throws ExceptionCombo 210 * @var string $fullQualifiedName - generally a short icon name (but it may be media id) 211 */ 212 public function __construct($fullQualifiedName, $tagAttributes = null) 213 { 214 215 $this->fullQualifiedName = $fullQualifiedName; 216 217 /** 218 * After optimization, the width and height of the svg are gone 219 * but the icon type set them again 220 * 221 * The icon type is used to set: 222 * * the default dimension 223 * * color styling 224 * * disable the responsive properties 225 * 226 */ 227 if ($tagAttributes === null) { 228 $tagAttributes = TagAttributes::createEmpty(); 229 } 230 $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE); 231 232 /** 233 * If the name have an extension, it's a file from the media directory 234 * Otherwise, it's an icon from a library 235 */ 236 $mediaDokuPath = DokuPath::createFromUnknownRoot($fullQualifiedName); 237 if (!empty($mediaDokuPath->getExtension())) { 238 239 // loop through candidates until a match was found: 240 // May be an icon from the templates 241 if (!FileSystems::exists($mediaDokuPath)) { 242 243 // Trying to see if it's not in the template images directory 244 $message = "The media file could not be found in the media library. If you want an icon from an icon library, indicate a name without extension."; 245 $message .= "<BR> Media File Library tested: $mediaDokuPath"; 246 throw new ExceptionCombo($message, self::ICON_CANONICAL_NAME); 247 248 249 } 250 251 parent::__construct($mediaDokuPath, $tagAttributes); 252 return; 253 254 } 255 256 257 /** 258 * Resource icon library 259 * {@link Icon::createFromComboResource()} 260 */ 261 if (strpos($fullQualifiedName, self::COMBO) === 0) { 262 $iconName = str_replace(self::COMBO . ":", "", $fullQualifiedName); 263 // the icon name is not to be found in the images directory (there is also brand) 264 // but can be anywhere below the resources directory 265 $mediaDokuPath = DokuPath::createComboResource("$iconName.svg"); 266 } else { 267 /** 268 * From an icon library 269 */ 270 $iconNameSpace = PluginUtility::getConfValue(self::CONF_ICONS_MEDIA_NAMESPACE, self::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT); 271 if (substr($iconNameSpace, 0, 1) != DokuPath::PATH_SEPARATOR) { 272 $iconNameSpace = DokuPath::PATH_SEPARATOR . $iconNameSpace; 273 } 274 if (substr($iconNameSpace, -1) != DokuPath::PATH_SEPARATOR) { 275 $iconNameSpace = $iconNameSpace . ":"; 276 } 277 278 $mediaPathId = $iconNameSpace . $fullQualifiedName . ".svg"; 279 $mediaDokuPath = DokuPath::createMediaPathFromAbsolutePath($mediaPathId); 280 } 281 282 283 // Bug: null file created when the stream could not get any byte 284 // We delete them 285 if (FileSystems::exists($mediaDokuPath)) { 286 if (FileSystems::getSize($mediaDokuPath) == 0) { 287 FileSystems::delete($mediaDokuPath); 288 } 289 } 290 291 /** 292 * Name parsing to extract the library name and icon name 293 */ 294 // default 295 $this->library = PluginUtility::getConfValue(self::CONF_DEFAULT_ICON_LIBRARY, self::CONF_DEFAULT_ICON_LIBRARY_DEFAULT); 296 $this->iconName = $this->fullQualifiedName; 297 // parse 298 $sepPosition = strpos($this->fullQualifiedName, ":"); 299 if ($sepPosition != false) { 300 $this->library = substr($this->fullQualifiedName, 0, $sepPosition); 301 $this->iconName = substr($this->fullQualifiedName, $sepPosition + 1); 302 } 303 304 parent::__construct($mediaDokuPath, $tagAttributes); 305 306 } 307 308 309 /** 310 * The function used to render an icon 311 * @param string $name - icon name 312 * @param TagAttributes|null $tagAttributes - the icon attributes 313 * @return Icon 314 * @throws ExceptionCombo 315 */ 316 static public function create(string $name, TagAttributes $tagAttributes = null): Icon 317 { 318 319 return new Icon($name, $tagAttributes); 320 321 } 322 323 /** 324 * @throws ExceptionCombo 325 */ 326 public static function createFromComboResource(string $name, TagAttributes $tagAttributes = null): Icon 327 { 328 return self::create(self::COMBO . ":$name", $tagAttributes); 329 } 330 331 /** 332 * @throws ExceptionCombo 333 */ 334 private static function getPhysicalNameFromDictionary(string $logicalName, string $library) 335 { 336 337 $jsonArray = Dictionary::getFrom("$library-icons"); 338 $physicalName = $jsonArray[$logicalName]; 339 if ($physicalName === null) { 340 LogUtility::msg("The icon ($logicalName) is unknown for the library ($library)"); 341 // by default, just lowercase 342 return strtolower($logicalName); 343 } 344 return $physicalName; 345 346 } 347 348 public function getFullQualifiedName(): string 349 { 350 return $this->fullQualifiedName; 351 } 352 353 /** 354 * @throws ExceptionCombo 355 */ 356 public function getDownloadUrl(): string 357 { 358 359 360 // Get the qualified library name 361 $library = $this->library; 362 $acronymLibraries = self::getLibraries(); 363 if (isset($acronymLibraries[$library])) { 364 $library = $acronymLibraries[$library]; 365 } 366 367 // Get the url 368 $iconLibraries = self::ICON_LIBRARY_URLS; 369 if (!isset($iconLibraries[$library])) { 370 throw new ExceptionCombo("The icon library ($library) is unknown. The icon could not be downloaded.", self::ICON_CANONICAL_NAME); 371 } else { 372 $iconBaseUrl = $iconLibraries[$library]; 373 } 374 375 /** 376 * Name processing 377 */ 378 $iconName = $this->iconName; 379 switch ($library) { 380 381 case self::FLAT_COLOR_ICON: 382 $iconName = str_replace("-", "_", $iconName); 383 break; 384 case self::TWEET_EMOJI: 385 try { 386 $iconName = self::getEmojiCodePoint($iconName); 387 } catch (ExceptionCombo $e) { 388 throw new ExceptionCombo("The emoji name $iconName is unknown. The emoji could not be downloaded.", self::ICON_CANONICAL_NAME, 0, $e); 389 } 390 break; 391 case self::ANT_DESIGN: 392 // table-outlined where table is the svg, outlined the category 393 // ordered-list-outlined where ordered-list is the svg, outlined the category 394 [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 395 $iconBaseUrl .= "/$iconType"; 396 break; 397 case self::CARBON: 398 /** 399 * Iconify normalized the name of the carbon library (making them lowercase) 400 * 401 * For instance, CSV is csv (https://icon-sets.iconify.design/carbon/csv/) 402 * 403 * This dictionary reproduce it. 404 */ 405 $iconName = self::getPhysicalNameFromDictionary($iconName, self::CARBON); 406 break; 407 case self::FAD: 408 $iconName = self::getPhysicalNameFromDictionary($iconName, self::FAD); 409 break; 410 case self::ICOMOON: 411 $iconName = self::getPhysicalNameFromDictionary($iconName, self::ICOMOON); 412 break; 413 case self::CORE_UI_BRAND: 414 $iconName = self::getPhysicalNameFromDictionary($iconName, self::CORE_UI_BRAND); 415 break; 416 case self::EVA: 417 // Eva 418 // example: eva:facebook-fill 419 [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 420 $iconBaseUrl .= "/$iconType/svg"; 421 if ($iconType === "outline") { 422 // for whatever reason, the name of outline icon has outline at the end 423 // and not for the fill icon 424 $iconName .= "-$iconType"; 425 } 426 break; 427 case self::PHOSPHOR_ICONS: 428 // example: activity-light 429 [$iconShortName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 430 $iconBaseUrl .= "/$iconType"; 431 break; 432 case self::SIMPLE_LINE: 433 // Bug 434 if ($iconName === "social-pinterest") { 435 $iconName = "social-pintarest"; 436 } 437 break; 438 case self::BOX_ICON: 439 [$iconType, $extractedIconName] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 440 switch ($iconType) { 441 case "bxl": 442 $iconBaseUrl .= "/logos"; 443 break; 444 case "bx": 445 $iconBaseUrl .= "/regular"; 446 break; 447 case "bxs": 448 $iconBaseUrl .= "/solid"; 449 break; 450 default: 451 throw new ExceptionCombo("The box-icon icon ($iconName) has a type ($iconType) that is unknown, we can't determine the location of the icon to download"); 452 } 453 break; 454 case self::VSCODE: 455 $iconName = str_replace("-", "_", $iconName); 456 break; 457 case self::SI_GLYPH: 458 $iconName = "si-glyph-" . $iconName; 459 break; 460 } 461 462 463 // The url 464 return "$iconBaseUrl/$iconName.svg"; 465 466 } 467 468 /** 469 * @throws ExceptionCombo 470 */ 471 public function download() 472 { 473 474 $mediaDokuPath = $this->getPath(); 475 if (!($mediaDokuPath instanceof DokuPath)) { 476 throw new ExceptionCombo("The icon path ($mediaDokuPath) is not a wiki path. This is not yet supported"); 477 } 478 $library = $this->getLibrary(); 479 480 /** 481 * Create the target directory if it does not exist 482 */ 483 $iconDir = $mediaDokuPath->getParent(); 484 if (!FileSystems::exists($iconDir)) { 485 try { 486 FileSystems::createDirectory($iconDir); 487 } catch (ExceptionCombo $e) { 488 throw new ExceptionCombo("The icon directory ($iconDir) could not be created.", self::ICON_CANONICAL_NAME, 0, $e); 489 } 490 } 491 492 /** 493 * Download the icon 494 */ 495 $downloadUrl = $this->getDownloadUrl(); 496 $filePointer = @fopen($downloadUrl, 'r'); 497 if ($filePointer == false) { 498 // (ie no icon file found at ($downloadUrl) 499 $urlLibrary = self::ICON_LIBRARY_WEBSITE_URLS[$library]; 500 throw new ExceptionCombo("The library (<a href=\"$urlLibrary\">$library</a>) does not have a icon (<a href=\"$downloadUrl\">$this->iconName</a>).", self::ICON_CANONICAL_NAME); 501 } 502 503 $numberOfByte = @file_put_contents($mediaDokuPath->toLocalPath()->toAbsolutePath()->toString(), $filePointer); 504 if ($numberOfByte != false) { 505 LogUtility::msg("The icon ($this) from the library ($library) was downloaded to ($mediaDokuPath)", LogUtility::LVL_MSG_INFO, self::ICON_CANONICAL_NAME); 506 } else { 507 LogUtility::msg("Internal error: The icon ($this) from the library ($library) could no be written to ($mediaDokuPath)", LogUtility::LVL_MSG_ERROR, self::ICON_CANONICAL_NAME); 508 } 509 510 511 } 512 513 /** 514 * @param $iconName 515 * @param $mediaFilePath 516 * @deprecated Old code to download icon from the material design api 517 */ 518 public 519 static function downloadIconFromMaterialDesignApi($iconName, $mediaFilePath) 520 { 521 // Try the official API 522 // Read the icon meta of 523 // Meta Json file got all icons 524 // 525 // * Available at: https://raw.githubusercontent.com/Templarian/MaterialDesign/master/meta.json 526 // * See doc: https://github.com/Templarian/MaterialDesign-Site/blob/master/src/content/api.md) 527 $arrayFormat = true; 528 $iconMetaJson = json_decode(file_get_contents(__DIR__ . '/../resources/dictionary/icon-meta.json'), $arrayFormat); 529 $iconId = null; 530 foreach ($iconMetaJson as $key => $value) { 531 if ($value['name'] == $iconName) { 532 $iconId = $value['id']; 533 break; 534 } 535 } 536 if ($iconId != null) { 537 538 // Download 539 // Call to the API 540 // https://dev.materialdesignicons.com/contribute/site/api 541 $downloadUrl = "https://materialdesignicons.com/api/download/icon/svg/$iconId"; 542 $filePointer = file_put_contents($mediaFilePath, fopen($downloadUrl, 'r')); 543 if ($filePointer == false) { 544 LogUtility::msg("The file ($downloadUrl) could not be downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_ERROR, self::ICON_CANONICAL_NAME); 545 } else { 546 LogUtility::msg("The material design icon ($iconName) was downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_INFO, self::ICON_CANONICAL_NAME); 547 } 548 549 } 550 551 } 552 553 private static function getLibraries(): array 554 { 555 return array_merge( 556 self::PUBLIC_LIBRARY_ACRONYM, 557 self::DEPRECATED_LIBRARY_ACRONYM 558 ); 559 } 560 561 /** 562 * @throws ExceptionCombo 563 */ 564 public static function getEmojiCodePoint(string $emojiName) 565 { 566 $path = Site::getComboDictionaryDirectory()->resolve("emojis.json"); 567 $jsonContent = FileSystems::getContent($path); 568 $jsonArray = Json::createFromString($jsonContent)->toArray(); 569 return $jsonArray[$emojiName]; 570 } 571 572 573 /** 574 * @param string $iconName 575 * @param string $sep 576 * @return array 577 * @throws ExceptionCombo 578 */ 579 private static function explodeInTwoPartsByLastPosition(string $iconName, string $sep = "-"): array 580 { 581 $index = strrpos($iconName, $sep); 582 if ($index === false) { 583 throw new ExceptionCombo ("We expect that the icon name ($iconName) has two parts separated by a `-` (example: table-outlined). The icon could not be downloaded.", self::ICON_CANONICAL_NAME); 584 } 585 $firstPart = substr($iconName, 0, $index); 586 $secondPart = substr($iconName, $index + 1); 587 return [$firstPart, $secondPart]; 588 } 589 590 591 /** 592 * @throws ExceptionCombo 593 */ 594 public function render(): string 595 { 596 597 if (!FileSystems::exists($this->getPath())) { 598 try { 599 $this->download(); 600 } catch (ExceptionCombo $e) { 601 throw new ExceptionCombo("The icon ($this) does not exist and could not be downloaded ({$e->getMessage()}.", self::ICON_CANONICAL_NAME); 602 } 603 } 604 605 $svgImageLink = SvgImageLink::createMediaLinkFromPath( 606 $this->getPath(), 607 $this->getAttributes() 608 ); 609 return $svgImageLink->renderMediaTag(); 610 611 612 } 613 614 public function __toString() 615 { 616 return $this->getFullQualifiedName(); 617 } 618 619 private function getLibrary() 620 { 621 return $this->library; 622 } 623 624 625} 626