137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 151fa8c418SNickeaurequire_once(__DIR__ . '/PluginUtility.php'); 1637748cd8SNickeau 1737748cd8SNickeau 1837748cd8SNickeau/** 1937748cd8SNickeau * Class IconUtility 2037748cd8SNickeau * @package ComboStrap 2137748cd8SNickeau * @see https://combostrap.com/icon 2237748cd8SNickeau * 2337748cd8SNickeau * 2437748cd8SNickeau * Material design does not have a repository structure where we can extract the location 2537748cd8SNickeau * from the name 2637748cd8SNickeau * https://material.io/resources/icons https://google.github.io/material-design-icons/ 2737748cd8SNickeau * 2837748cd8SNickeau * Injection via javascript to avoid problem with the php svgsimple library 2937748cd8SNickeau * https://www.npmjs.com/package/svg-injector 3037748cd8SNickeau */ 3182a60d03SNickeauclass Icon extends ImageSvg 3237748cd8SNickeau{ 3337748cd8SNickeau const CONF_ICONS_MEDIA_NAMESPACE = "icons_namespace"; 3437748cd8SNickeau const CONF_ICONS_MEDIA_NAMESPACE_DEFAULT = ":" . PluginUtility::COMBOSTRAP_NAMESPACE_NAME . ":icons"; 3537748cd8SNickeau // Canonical name 36*4cadd4f8SNickeau const ICON_CANONICAL_NAME = "icon"; 3737748cd8SNickeau 3837748cd8SNickeau 3937748cd8SNickeau const ICON_LIBRARY_URLS = array( 4082a60d03SNickeau self::ANT_DESIGN => "https://raw.githubusercontent.com/ant-design/ant-design-icons/master/packages/icons-svg/svg", 41*4cadd4f8SNickeau self::BOOTSTRAP => "https://raw.githubusercontent.com/twbs/icons/main/icons", 42*4cadd4f8SNickeau self::CARBON => "https://raw.githubusercontent.com/carbon-design-system/carbon/main/packages/icons/src/svg/32", 4382a60d03SNickeau self::CLARITY => "https://raw.githubusercontent.com/vmware/clarity-assets/master/icons/essential", 44*4cadd4f8SNickeau self::CODE_ICON => "https://raw.githubusercontent.com/microsoft/vscode-codicons/main/src/icons", 45*4cadd4f8SNickeau self::ELEGANT_THEME => "https://raw.githubusercontent.com/pprince/etlinefont-bower/master/images/svg/individual_icons", 46*4cadd4f8SNickeau self::ENTYPO => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo", 47*4cadd4f8SNickeau self::ENTYPO_SOCIAL => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo%20Social%20Extension", 48*4cadd4f8SNickeau self::EVA => "https://raw.githubusercontent.com/akveo/eva-icons/master/package/icons", 49*4cadd4f8SNickeau self::FEATHER => "https://raw.githubusercontent.com/feathericons/feather/master/icons", 50*4cadd4f8SNickeau self::FAD => "https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs", 51*4cadd4f8SNickeau self::ICONSCOUT => "https://raw.githubusercontent.com/Iconscout/unicons/master/svg/line", 52*4cadd4f8SNickeau self::LOGOS => "https://raw.githubusercontent.com/gilbarbara/logos/master/logos", 53*4cadd4f8SNickeau self::MATERIAL_DESIGN => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg", 54*4cadd4f8SNickeau self::OCTICON => "https://raw.githubusercontent.com/primer/octicons/main/icons", 55*4cadd4f8SNickeau self::TWEET_EMOJI => "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg", 56*4cadd4f8SNickeau self::SIMPLE_LINE => "https://raw.githubusercontent.com/thesabbir/simple-line-icons/master/src/svgs", 57*4cadd4f8SNickeau self::ICOMOON => "https://raw.githubusercontent.com/Keyamoon/IcoMoon-Free/master/SVG", 58*4cadd4f8SNickeau self::DASHICONS => "https://raw.githubusercontent.com/WordPress/dashicons/master/svg-min", 59*4cadd4f8SNickeau self::ICONOIR => "https://raw.githubusercontent.com/lucaburgio/iconoir/master/icons", 60*4cadd4f8SNickeau self::BOX_ICON => "https://raw.githubusercontent.com/atisawd/boxicons/master/svg", 61*4cadd4f8SNickeau self::LINE_AWESOME => "https://raw.githubusercontent.com/icons8/line-awesome/master/svg", 62*4cadd4f8SNickeau self::FONT_AWESOME_SOLID => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/solid", 63*4cadd4f8SNickeau self::FONT_AWESOME_BRANDS => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/brands", 64*4cadd4f8SNickeau self::FONT_AWESOME_REGULAR => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/regular", 65*4cadd4f8SNickeau self::VAADIN => "https://raw.githubusercontent.com/vaadin/vaadin-icons/master/assets/svg", 66*4cadd4f8SNickeau self::CORE_UI_BRAND => "https://raw.githubusercontent.com/coreui/coreui-icons/master/svg/brand", 67*4cadd4f8SNickeau self::FLAT_COLOR_ICON => "https://raw.githubusercontent.com/icons8/flat-color-icons/master/svg", 68*4cadd4f8SNickeau self::PHOSPHOR_ICONS => "https://raw.githubusercontent.com/phosphor-icons/phosphor-icons/master/assets", 69*4cadd4f8SNickeau self::VSCODE => "https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons", 70*4cadd4f8SNickeau self::SI_GLYPH => "https://raw.githubusercontent.com/frexy/glyph-iconset/master/svg", 71*4cadd4f8SNickeau self::AKAR_ICONS => "https://raw.githubusercontent.com/artcoholic/akar-icons/master/src/svg" 7237748cd8SNickeau ); 7337748cd8SNickeau 7437748cd8SNickeau const ICON_LIBRARY_WEBSITE_URLS = array( 7537748cd8SNickeau self::BOOTSTRAP => "https://icons.getbootstrap.com/", 7637748cd8SNickeau self::MATERIAL_DESIGN => "https://materialdesignicons.com/", 77c3437056SNickeau self::FEATHER => "https://feathericons.com/", 78c3437056SNickeau self::CODE_ICON => "https://microsoft.github.io/vscode-codicons/", 79c3437056SNickeau self::LOGOS => "https://svgporn.com/", 8082a60d03SNickeau self::CARBON => "https://www.carbondesignsystem.com/guidelines/icons/library/", 8182a60d03SNickeau self::TWEET_EMOJI => "https://twemoji.twitter.com/", 8282a60d03SNickeau self::ANT_DESIGN => "https://ant.design/components/icon/", 8382a60d03SNickeau self::CLARITY => "https://clarity.design/foundation/icons/", 84*4cadd4f8SNickeau self::OCTICON => "https://primer.style/octicons/", 85*4cadd4f8SNickeau self::ICONSCOUT => "https://iconscout.com/unicons/explore/line", 86*4cadd4f8SNickeau self::ELEGANT_THEME => "https://github.com/pprince/etlinefont-bower", 87*4cadd4f8SNickeau self::EVA => "https://akveo.github.io/eva-icons/", 88*4cadd4f8SNickeau self::ENTYPO_SOCIAL => "http://www.entypo.com", 89*4cadd4f8SNickeau self::ENTYPO => "http://www.entypo.com", 90*4cadd4f8SNickeau self::SIMPLE_LINE => "https://thesabbir.github.io/simple-line-icons", 91*4cadd4f8SNickeau self::ICOMOON => "https://icomoon.io/", 92*4cadd4f8SNickeau self::DASHICONS => "https://developer.wordpress.org/resource/dashicons/", 93*4cadd4f8SNickeau self::ICONOIR => "https://iconoir.com", 94*4cadd4f8SNickeau self::BOX_ICON => "https://boxicons.com", 95*4cadd4f8SNickeau self::LINE_AWESOME => "https://icons8.com/line-awesome", 96*4cadd4f8SNickeau self::FONT_AWESOME => "https://fontawesome.com/", 97*4cadd4f8SNickeau self::FONT_AWESOME_SOLID => "https://fontawesome.com/", 98*4cadd4f8SNickeau self::FONT_AWESOME_BRANDS => "https://fontawesome.com/", 99*4cadd4f8SNickeau self::FONT_AWESOME_REGULAR => "https://fontawesome.com/", 100*4cadd4f8SNickeau self::VAADIN => "https://vaadin.com/icons", 101*4cadd4f8SNickeau self::CORE_UI_BRAND => "https://coreui.io/icons/", 102*4cadd4f8SNickeau self::FLAT_COLOR_ICON => "https://icons8.com/icons/color", 103*4cadd4f8SNickeau self::PHOSPHOR_ICONS => "https://phosphoricons.com/", 104*4cadd4f8SNickeau self::VSCODE => "https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons", 105*4cadd4f8SNickeau self::SI_GLYPH => "https://glyph.smarticons.co/", 106*4cadd4f8SNickeau self::AKAR_ICONS => "https://akaricons.com/" 107*4cadd4f8SNickeau 10837748cd8SNickeau ); 10937748cd8SNickeau 11037748cd8SNickeau const CONF_DEFAULT_ICON_LIBRARY = "defaultIconLibrary"; 111c3437056SNickeau const CONF_DEFAULT_ICON_LIBRARY_DEFAULT = self::MATERIAL_DESIGN_ACRONYM; 112c3437056SNickeau 113c3437056SNickeau /** 114c3437056SNickeau * Deprecated library acronym / name 115c3437056SNickeau */ 116c3437056SNickeau const DEPRECATED_LIBRARY_ACRONYM = array( 117c3437056SNickeau "bs" => self::BOOTSTRAP, // old one (deprecated) - the good acronym is bi (seen also in the class) 118c3437056SNickeau "md" => self::MATERIAL_DESIGN 11937748cd8SNickeau ); 120c3437056SNickeau 121c3437056SNickeau /** 122c3437056SNickeau * Public known acronym / name (Used in the configuration) 123c3437056SNickeau */ 124c3437056SNickeau const PUBLIC_LIBRARY_ACRONYM = array( 125c3437056SNickeau "bi" => self::BOOTSTRAP, 126c3437056SNickeau self::MATERIAL_DESIGN_ACRONYM => self::MATERIAL_DESIGN, 127c3437056SNickeau "fe" => self::FEATHER, 128c3437056SNickeau "codicon" => self::CODE_ICON, 129c3437056SNickeau "logos" => self::LOGOS, 13082a60d03SNickeau "carbon" => self::CARBON, 13182a60d03SNickeau "twemoji" => self::TWEET_EMOJI, 13282a60d03SNickeau "ant-design" => self::ANT_DESIGN, 13382a60d03SNickeau "fad" => self::FAD, 13482a60d03SNickeau "clarity" => self::CLARITY, 135*4cadd4f8SNickeau "octicon" => self::OCTICON, 136*4cadd4f8SNickeau "uit" => self::ICONSCOUT, 137*4cadd4f8SNickeau "et" => self::ELEGANT_THEME, 138*4cadd4f8SNickeau "eva" => self::EVA, 139*4cadd4f8SNickeau "entypo-social" => self::ENTYPO_SOCIAL, 140*4cadd4f8SNickeau "entypo" => self::ENTYPO, 141*4cadd4f8SNickeau "simple-line-icons" => self::SIMPLE_LINE, 142*4cadd4f8SNickeau "icomoon-free" => self::ICOMOON, 143*4cadd4f8SNickeau "dashicons" => self::DASHICONS, 144*4cadd4f8SNickeau "iconoir" => self::ICONOIR, 145*4cadd4f8SNickeau "bx" => self::BOX_ICON, 146*4cadd4f8SNickeau "la" => self::LINE_AWESOME, 147*4cadd4f8SNickeau "fa-solid" => self::FONT_AWESOME_SOLID, 148*4cadd4f8SNickeau "fa-brands" => self::FONT_AWESOME_BRANDS, 149*4cadd4f8SNickeau "fa-regular" => self::FONT_AWESOME_REGULAR, 150*4cadd4f8SNickeau "vaadin" => self::VAADIN, 151*4cadd4f8SNickeau "cib" => self::CORE_UI_BRAND, 152*4cadd4f8SNickeau "flat-color-icons" => self::FLAT_COLOR_ICON, 153*4cadd4f8SNickeau "ph" => self::PHOSPHOR_ICONS, 154*4cadd4f8SNickeau "vscode-icons" => self::VSCODE, 155*4cadd4f8SNickeau "si-glyph" => self::SI_GLYPH, 156*4cadd4f8SNickeau "akar-icons" => self::AKAR_ICONS 157c3437056SNickeau ); 158c3437056SNickeau 15937748cd8SNickeau const FEATHER = "feather"; 16037748cd8SNickeau const BOOTSTRAP = "bootstrap"; 16137748cd8SNickeau const MATERIAL_DESIGN = "material-design"; 162c3437056SNickeau const CODE_ICON = "codicon"; 163c3437056SNickeau const LOGOS = "logos"; 164c3437056SNickeau const CARBON = "carbon"; 165c3437056SNickeau const MATERIAL_DESIGN_ACRONYM = "mdi"; 16682a60d03SNickeau const TWEET_EMOJI = "twemoji"; 16782a60d03SNickeau const ANT_DESIGN = "ant-design"; 16882a60d03SNickeau const FAD = "fad"; 16982a60d03SNickeau const CLARITY = "clarity"; 17082a60d03SNickeau const OCTICON = "octicon"; 171*4cadd4f8SNickeau const ICONSCOUT = "iconscout"; 172*4cadd4f8SNickeau const ELEGANT_THEME = "elegant-theme"; 173*4cadd4f8SNickeau const EVA = "eva"; 174*4cadd4f8SNickeau const ENTYPO_SOCIAL = "entypo-social"; 175*4cadd4f8SNickeau const ENTYPO = "entypo"; 176*4cadd4f8SNickeau const SIMPLE_LINE = "simple-line"; 177*4cadd4f8SNickeau const ICOMOON = "icomoon"; 178*4cadd4f8SNickeau const DASHICONS = " dashicons"; 179*4cadd4f8SNickeau const ICONOIR = "iconoir"; 180*4cadd4f8SNickeau const BOX_ICON = "box-icon"; 181*4cadd4f8SNickeau const LINE_AWESOME = "line-awesome"; 182*4cadd4f8SNickeau const FONT_AWESOME_SOLID = "font-awesome-solid"; 183*4cadd4f8SNickeau const FONT_AWESOME_BRANDS = "font-awesome-brands"; 184*4cadd4f8SNickeau const FONT_AWESOME_REGULAR = "font-awesome-regular"; 185*4cadd4f8SNickeau const FONT_AWESOME = "font-awesome"; 186*4cadd4f8SNickeau const VAADIN = "vaadin"; 187*4cadd4f8SNickeau const CORE_UI_BRAND = "cib"; 188*4cadd4f8SNickeau const FLAT_COLOR_ICON = "flat-color-icons"; 189*4cadd4f8SNickeau const PHOSPHOR_ICONS = "ph"; 190*4cadd4f8SNickeau const VSCODE = "vscode"; 191*4cadd4f8SNickeau const SI_GLYPH = "si-glyph"; 192*4cadd4f8SNickeau const COMBO = DokuPath::COMBO_DRIVE; 193*4cadd4f8SNickeau const AKAR_ICONS = "akar-icons"; 19437748cd8SNickeau 19537748cd8SNickeau 196*4cadd4f8SNickeau private $fullQualifiedName; 197*4cadd4f8SNickeau /** 198*4cadd4f8SNickeau * The icon library 199*4cadd4f8SNickeau * @var mixed|null 200*4cadd4f8SNickeau */ 201*4cadd4f8SNickeau private $library; 202*4cadd4f8SNickeau /** 203*4cadd4f8SNickeau * @var false|string 204*4cadd4f8SNickeau */ 205*4cadd4f8SNickeau private $iconName; 206*4cadd4f8SNickeau 20737748cd8SNickeau /** 208*4cadd4f8SNickeau * Icon constructor. 20982a60d03SNickeau * @throws ExceptionCombo 210*4cadd4f8SNickeau * @var string $fullQualifiedName - generally a short icon name (but it may be media id) 21137748cd8SNickeau */ 212*4cadd4f8SNickeau public function __construct($fullQualifiedName, $tagAttributes = null) 21337748cd8SNickeau { 21437748cd8SNickeau 215*4cadd4f8SNickeau $this->fullQualifiedName = $fullQualifiedName; 21637748cd8SNickeau 21737748cd8SNickeau /** 21837748cd8SNickeau * After optimization, the width and height of the svg are gone 21937748cd8SNickeau * but the icon type set them again 22037748cd8SNickeau * 22137748cd8SNickeau * The icon type is used to set: 22237748cd8SNickeau * * the default dimension 22337748cd8SNickeau * * color styling 22437748cd8SNickeau * * disable the responsive properties 22537748cd8SNickeau * 22637748cd8SNickeau */ 227*4cadd4f8SNickeau if ($tagAttributes === null) { 228*4cadd4f8SNickeau $tagAttributes = TagAttributes::createEmpty(); 229*4cadd4f8SNickeau } 23082a60d03SNickeau $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE); 23137748cd8SNickeau 232*4cadd4f8SNickeau /** 233*4cadd4f8SNickeau * If the name have an extension, it's a file from the media directory 234*4cadd4f8SNickeau * Otherwise, it's an icon from a library 235*4cadd4f8SNickeau */ 236*4cadd4f8SNickeau $mediaDokuPath = DokuPath::createFromUnknownRoot($fullQualifiedName); 237*4cadd4f8SNickeau if (!empty($mediaDokuPath->getExtension())) { 238*4cadd4f8SNickeau 239*4cadd4f8SNickeau // loop through candidates until a match was found: 240*4cadd4f8SNickeau // May be an icon from the templates 241*4cadd4f8SNickeau if (!FileSystems::exists($mediaDokuPath)) { 242*4cadd4f8SNickeau 243*4cadd4f8SNickeau // Trying to see if it's not in the template images directory 244*4cadd4f8SNickeau $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*4cadd4f8SNickeau $message .= "<BR> Media File Library tested: $mediaDokuPath"; 246*4cadd4f8SNickeau throw new ExceptionCombo($message, self::ICON_CANONICAL_NAME); 247*4cadd4f8SNickeau 248*4cadd4f8SNickeau 249*4cadd4f8SNickeau } 250*4cadd4f8SNickeau 251*4cadd4f8SNickeau parent::__construct($mediaDokuPath, $tagAttributes); 252*4cadd4f8SNickeau return; 253*4cadd4f8SNickeau 254*4cadd4f8SNickeau } 255*4cadd4f8SNickeau 256*4cadd4f8SNickeau 257*4cadd4f8SNickeau /** 258*4cadd4f8SNickeau * Resource icon library 259*4cadd4f8SNickeau * {@link Icon::createFromComboResource()} 260*4cadd4f8SNickeau */ 261*4cadd4f8SNickeau if (strpos($fullQualifiedName, self::COMBO) === 0) { 262*4cadd4f8SNickeau $iconName = str_replace(self::COMBO . ":", "", $fullQualifiedName); 263*4cadd4f8SNickeau // the icon name is not to be found in the images directory (there is also brand) 264*4cadd4f8SNickeau // but can be anywhere below the resources directory 265*4cadd4f8SNickeau $mediaDokuPath = DokuPath::createComboResource("$iconName.svg"); 266*4cadd4f8SNickeau } else { 267*4cadd4f8SNickeau /** 268*4cadd4f8SNickeau * From an icon library 269*4cadd4f8SNickeau */ 270*4cadd4f8SNickeau $iconNameSpace = PluginUtility::getConfValue(self::CONF_ICONS_MEDIA_NAMESPACE, self::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT); 271*4cadd4f8SNickeau if (substr($iconNameSpace, 0, 1) != DokuPath::PATH_SEPARATOR) { 272*4cadd4f8SNickeau $iconNameSpace = DokuPath::PATH_SEPARATOR . $iconNameSpace; 273*4cadd4f8SNickeau } 274*4cadd4f8SNickeau if (substr($iconNameSpace, -1) != DokuPath::PATH_SEPARATOR) { 275*4cadd4f8SNickeau $iconNameSpace = $iconNameSpace . ":"; 276*4cadd4f8SNickeau } 277*4cadd4f8SNickeau 278*4cadd4f8SNickeau $mediaPathId = $iconNameSpace . $fullQualifiedName . ".svg"; 279*4cadd4f8SNickeau $mediaDokuPath = DokuPath::createMediaPathFromAbsolutePath($mediaPathId); 280*4cadd4f8SNickeau } 281*4cadd4f8SNickeau 282*4cadd4f8SNickeau 283*4cadd4f8SNickeau // Bug: null file created when the stream could not get any byte 284*4cadd4f8SNickeau // We delete them 285*4cadd4f8SNickeau if (FileSystems::exists($mediaDokuPath)) { 286*4cadd4f8SNickeau if (FileSystems::getSize($mediaDokuPath) == 0) { 287*4cadd4f8SNickeau FileSystems::delete($mediaDokuPath); 288*4cadd4f8SNickeau } 289*4cadd4f8SNickeau } 290*4cadd4f8SNickeau 291*4cadd4f8SNickeau /** 292*4cadd4f8SNickeau * Name parsing to extract the library name and icon name 293*4cadd4f8SNickeau */ 294*4cadd4f8SNickeau // default 295*4cadd4f8SNickeau $this->library = PluginUtility::getConfValue(self::CONF_DEFAULT_ICON_LIBRARY, self::CONF_DEFAULT_ICON_LIBRARY_DEFAULT); 296*4cadd4f8SNickeau $this->iconName = $this->fullQualifiedName; 297*4cadd4f8SNickeau // parse 298*4cadd4f8SNickeau $sepPosition = strpos($this->fullQualifiedName, ":"); 299*4cadd4f8SNickeau if ($sepPosition != false) { 300*4cadd4f8SNickeau $this->library = substr($this->fullQualifiedName, 0, $sepPosition); 301*4cadd4f8SNickeau $this->iconName = substr($this->fullQualifiedName, $sepPosition + 1); 302*4cadd4f8SNickeau } 303*4cadd4f8SNickeau 304*4cadd4f8SNickeau parent::__construct($mediaDokuPath, $tagAttributes); 305*4cadd4f8SNickeau 306*4cadd4f8SNickeau } 307*4cadd4f8SNickeau 308*4cadd4f8SNickeau 309*4cadd4f8SNickeau /** 310*4cadd4f8SNickeau * The function used to render an icon 311*4cadd4f8SNickeau * @param string $name - icon name 312*4cadd4f8SNickeau * @param TagAttributes|null $tagAttributes - the icon attributes 313*4cadd4f8SNickeau * @return Icon 314*4cadd4f8SNickeau * @throws ExceptionCombo 315*4cadd4f8SNickeau */ 316*4cadd4f8SNickeau static public function create(string $name, TagAttributes $tagAttributes = null): Icon 317*4cadd4f8SNickeau { 318*4cadd4f8SNickeau 319*4cadd4f8SNickeau return new Icon($name, $tagAttributes); 320*4cadd4f8SNickeau 321*4cadd4f8SNickeau } 322*4cadd4f8SNickeau 323*4cadd4f8SNickeau /** 324*4cadd4f8SNickeau * @throws ExceptionCombo 325*4cadd4f8SNickeau */ 326*4cadd4f8SNickeau public static function createFromComboResource(string $name, TagAttributes $tagAttributes = null): Icon 327*4cadd4f8SNickeau { 328*4cadd4f8SNickeau return self::create(self::COMBO . ":$name", $tagAttributes); 329*4cadd4f8SNickeau } 330*4cadd4f8SNickeau 331*4cadd4f8SNickeau /** 332*4cadd4f8SNickeau * @throws ExceptionCombo 333*4cadd4f8SNickeau */ 334*4cadd4f8SNickeau private static function getPhysicalNameFromDictionary(string $logicalName, string $library) 335*4cadd4f8SNickeau { 336*4cadd4f8SNickeau 337*4cadd4f8SNickeau $jsonArray = Dictionary::getFrom("$library-icons"); 338*4cadd4f8SNickeau $physicalName = $jsonArray[$logicalName]; 339*4cadd4f8SNickeau if ($physicalName === null) { 340*4cadd4f8SNickeau LogUtility::msg("The icon ($logicalName) is unknown for the library ($library)"); 341*4cadd4f8SNickeau // by default, just lowercase 342*4cadd4f8SNickeau return strtolower($logicalName); 343*4cadd4f8SNickeau } 344*4cadd4f8SNickeau return $physicalName; 345*4cadd4f8SNickeau 346*4cadd4f8SNickeau } 347*4cadd4f8SNickeau 348*4cadd4f8SNickeau public function getFullQualifiedName(): string 349*4cadd4f8SNickeau { 350*4cadd4f8SNickeau return $this->fullQualifiedName; 351*4cadd4f8SNickeau } 352*4cadd4f8SNickeau 353*4cadd4f8SNickeau /** 354*4cadd4f8SNickeau * @throws ExceptionCombo 355*4cadd4f8SNickeau */ 356*4cadd4f8SNickeau public function getDownloadUrl(): string 357*4cadd4f8SNickeau { 358*4cadd4f8SNickeau 359*4cadd4f8SNickeau 360*4cadd4f8SNickeau // Get the qualified library name 361*4cadd4f8SNickeau $library = $this->library; 362*4cadd4f8SNickeau $acronymLibraries = self::getLibraries(); 363*4cadd4f8SNickeau if (isset($acronymLibraries[$library])) { 364*4cadd4f8SNickeau $library = $acronymLibraries[$library]; 365*4cadd4f8SNickeau } 366*4cadd4f8SNickeau 367*4cadd4f8SNickeau // Get the url 368*4cadd4f8SNickeau $iconLibraries = self::ICON_LIBRARY_URLS; 369*4cadd4f8SNickeau if (!isset($iconLibraries[$library])) { 370*4cadd4f8SNickeau throw new ExceptionCombo("The icon library ($library) is unknown. The icon could not be downloaded.", self::ICON_CANONICAL_NAME); 371*4cadd4f8SNickeau } else { 372*4cadd4f8SNickeau $iconBaseUrl = $iconLibraries[$library]; 373*4cadd4f8SNickeau } 374*4cadd4f8SNickeau 375*4cadd4f8SNickeau /** 376*4cadd4f8SNickeau * Name processing 377*4cadd4f8SNickeau */ 378*4cadd4f8SNickeau $iconName = $this->iconName; 379*4cadd4f8SNickeau switch ($library) { 380*4cadd4f8SNickeau 381*4cadd4f8SNickeau case self::FLAT_COLOR_ICON: 382*4cadd4f8SNickeau $iconName = str_replace("-", "_", $iconName); 383*4cadd4f8SNickeau break; 384*4cadd4f8SNickeau case self::TWEET_EMOJI: 385*4cadd4f8SNickeau try { 386*4cadd4f8SNickeau $iconName = self::getEmojiCodePoint($iconName); 387*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 388*4cadd4f8SNickeau throw new ExceptionCombo("The emoji name $iconName is unknown. The emoji could not be downloaded.", self::ICON_CANONICAL_NAME, 0, $e); 389*4cadd4f8SNickeau } 390*4cadd4f8SNickeau break; 391*4cadd4f8SNickeau case self::ANT_DESIGN: 392*4cadd4f8SNickeau // table-outlined where table is the svg, outlined the category 393*4cadd4f8SNickeau // ordered-list-outlined where ordered-list is the svg, outlined the category 394*4cadd4f8SNickeau [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 395*4cadd4f8SNickeau $iconBaseUrl .= "/$iconType"; 396*4cadd4f8SNickeau break; 397*4cadd4f8SNickeau case self::CARBON: 398*4cadd4f8SNickeau /** 399*4cadd4f8SNickeau * Iconify normalized the name of the carbon library (making them lowercase) 400*4cadd4f8SNickeau * 401*4cadd4f8SNickeau * For instance, CSV is csv (https://icon-sets.iconify.design/carbon/csv/) 402*4cadd4f8SNickeau * 403*4cadd4f8SNickeau * This dictionary reproduce it. 404*4cadd4f8SNickeau */ 405*4cadd4f8SNickeau $iconName = self::getPhysicalNameFromDictionary($iconName, self::CARBON); 406*4cadd4f8SNickeau break; 407*4cadd4f8SNickeau case self::FAD: 408*4cadd4f8SNickeau $iconName = self::getPhysicalNameFromDictionary($iconName, self::FAD); 409*4cadd4f8SNickeau break; 410*4cadd4f8SNickeau case self::ICOMOON: 411*4cadd4f8SNickeau $iconName = self::getPhysicalNameFromDictionary($iconName, self::ICOMOON); 412*4cadd4f8SNickeau break; 413*4cadd4f8SNickeau case self::CORE_UI_BRAND: 414*4cadd4f8SNickeau $iconName = self::getPhysicalNameFromDictionary($iconName, self::CORE_UI_BRAND); 415*4cadd4f8SNickeau break; 416*4cadd4f8SNickeau case self::EVA: 417*4cadd4f8SNickeau // Eva 418*4cadd4f8SNickeau // example: eva:facebook-fill 419*4cadd4f8SNickeau [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 420*4cadd4f8SNickeau $iconBaseUrl .= "/$iconType/svg"; 421*4cadd4f8SNickeau if ($iconType === "outline") { 422*4cadd4f8SNickeau // for whatever reason, the name of outline icon has outline at the end 423*4cadd4f8SNickeau // and not for the fill icon 424*4cadd4f8SNickeau $iconName .= "-$iconType"; 425*4cadd4f8SNickeau } 426*4cadd4f8SNickeau break; 427*4cadd4f8SNickeau case self::PHOSPHOR_ICONS: 428*4cadd4f8SNickeau // example: activity-light 429*4cadd4f8SNickeau [$iconShortName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 430*4cadd4f8SNickeau $iconBaseUrl .= "/$iconType"; 431*4cadd4f8SNickeau break; 432*4cadd4f8SNickeau case self::SIMPLE_LINE: 433*4cadd4f8SNickeau // Bug 434*4cadd4f8SNickeau if ($iconName === "social-pinterest") { 435*4cadd4f8SNickeau $iconName = "social-pintarest"; 436*4cadd4f8SNickeau } 437*4cadd4f8SNickeau break; 438*4cadd4f8SNickeau case self::BOX_ICON: 439*4cadd4f8SNickeau [$iconType, $extractedIconName] = self::explodeInTwoPartsByLastPosition($iconName, "-"); 440*4cadd4f8SNickeau switch ($iconType) { 441*4cadd4f8SNickeau case "bxl": 442*4cadd4f8SNickeau $iconBaseUrl .= "/logos"; 443*4cadd4f8SNickeau break; 444*4cadd4f8SNickeau case "bx": 445*4cadd4f8SNickeau $iconBaseUrl .= "/regular"; 446*4cadd4f8SNickeau break; 447*4cadd4f8SNickeau case "bxs": 448*4cadd4f8SNickeau $iconBaseUrl .= "/solid"; 449*4cadd4f8SNickeau break; 450*4cadd4f8SNickeau default: 451*4cadd4f8SNickeau 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*4cadd4f8SNickeau } 453*4cadd4f8SNickeau break; 454*4cadd4f8SNickeau case self::VSCODE: 455*4cadd4f8SNickeau $iconName = str_replace("-", "_", $iconName); 456*4cadd4f8SNickeau break; 457*4cadd4f8SNickeau case self::SI_GLYPH: 458*4cadd4f8SNickeau $iconName = "si-glyph-" . $iconName; 459*4cadd4f8SNickeau break; 460*4cadd4f8SNickeau } 461*4cadd4f8SNickeau 462*4cadd4f8SNickeau 463*4cadd4f8SNickeau // The url 464*4cadd4f8SNickeau return "$iconBaseUrl/$iconName.svg"; 465*4cadd4f8SNickeau 466*4cadd4f8SNickeau } 467*4cadd4f8SNickeau 468*4cadd4f8SNickeau /** 469*4cadd4f8SNickeau * @throws ExceptionCombo 470*4cadd4f8SNickeau */ 471*4cadd4f8SNickeau public function download() 472*4cadd4f8SNickeau { 473*4cadd4f8SNickeau 474*4cadd4f8SNickeau $mediaDokuPath = $this->getPath(); 475*4cadd4f8SNickeau if (!($mediaDokuPath instanceof DokuPath)) { 476*4cadd4f8SNickeau throw new ExceptionCombo("The icon path ($mediaDokuPath) is not a wiki path. This is not yet supported"); 477*4cadd4f8SNickeau } 478*4cadd4f8SNickeau $library = $this->getLibrary(); 479*4cadd4f8SNickeau 480*4cadd4f8SNickeau /** 481*4cadd4f8SNickeau * Create the target directory if it does not exist 482*4cadd4f8SNickeau */ 483*4cadd4f8SNickeau $iconDir = $mediaDokuPath->getParent(); 484*4cadd4f8SNickeau if (!FileSystems::exists($iconDir)) { 485*4cadd4f8SNickeau try { 486*4cadd4f8SNickeau FileSystems::createDirectory($iconDir); 487*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 488*4cadd4f8SNickeau throw new ExceptionCombo("The icon directory ($iconDir) could not be created.", self::ICON_CANONICAL_NAME, 0, $e); 489*4cadd4f8SNickeau } 490*4cadd4f8SNickeau } 491*4cadd4f8SNickeau 492*4cadd4f8SNickeau /** 493*4cadd4f8SNickeau * Download the icon 494*4cadd4f8SNickeau */ 495*4cadd4f8SNickeau $downloadUrl = $this->getDownloadUrl(); 496*4cadd4f8SNickeau $filePointer = @fopen($downloadUrl, 'r'); 497*4cadd4f8SNickeau if ($filePointer == false) { 498*4cadd4f8SNickeau // (ie no icon file found at ($downloadUrl) 499*4cadd4f8SNickeau $urlLibrary = self::ICON_LIBRARY_WEBSITE_URLS[$library]; 500*4cadd4f8SNickeau 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*4cadd4f8SNickeau } 502*4cadd4f8SNickeau 503*4cadd4f8SNickeau $numberOfByte = @file_put_contents($mediaDokuPath->toLocalPath()->toAbsolutePath()->toString(), $filePointer); 504*4cadd4f8SNickeau if ($numberOfByte != false) { 505*4cadd4f8SNickeau LogUtility::msg("The icon ($this) from the library ($library) was downloaded to ($mediaDokuPath)", LogUtility::LVL_MSG_INFO, self::ICON_CANONICAL_NAME); 506*4cadd4f8SNickeau } else { 507*4cadd4f8SNickeau 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*4cadd4f8SNickeau } 509*4cadd4f8SNickeau 51037748cd8SNickeau 51137748cd8SNickeau } 51237748cd8SNickeau 51337748cd8SNickeau /** 51437748cd8SNickeau * @param $iconName 51537748cd8SNickeau * @param $mediaFilePath 51637748cd8SNickeau * @deprecated Old code to download icon from the material design api 51737748cd8SNickeau */ 51837748cd8SNickeau public 51937748cd8SNickeau static function downloadIconFromMaterialDesignApi($iconName, $mediaFilePath) 52037748cd8SNickeau { 52137748cd8SNickeau // Try the official API 52237748cd8SNickeau // Read the icon meta of 52337748cd8SNickeau // Meta Json file got all icons 52437748cd8SNickeau // 52537748cd8SNickeau // * Available at: https://raw.githubusercontent.com/Templarian/MaterialDesign/master/meta.json 52637748cd8SNickeau // * See doc: https://github.com/Templarian/MaterialDesign-Site/blob/master/src/content/api.md) 52737748cd8SNickeau $arrayFormat = true; 52882a60d03SNickeau $iconMetaJson = json_decode(file_get_contents(__DIR__ . '/../resources/dictionary/icon-meta.json'), $arrayFormat); 52937748cd8SNickeau $iconId = null; 53037748cd8SNickeau foreach ($iconMetaJson as $key => $value) { 53137748cd8SNickeau if ($value['name'] == $iconName) { 53237748cd8SNickeau $iconId = $value['id']; 53337748cd8SNickeau break; 53437748cd8SNickeau } 53537748cd8SNickeau } 53637748cd8SNickeau if ($iconId != null) { 53737748cd8SNickeau 53837748cd8SNickeau // Download 53937748cd8SNickeau // Call to the API 54037748cd8SNickeau // https://dev.materialdesignicons.com/contribute/site/api 54137748cd8SNickeau $downloadUrl = "https://materialdesignicons.com/api/download/icon/svg/$iconId"; 54237748cd8SNickeau $filePointer = file_put_contents($mediaFilePath, fopen($downloadUrl, 'r')); 54337748cd8SNickeau if ($filePointer == false) { 544*4cadd4f8SNickeau LogUtility::msg("The file ($downloadUrl) could not be downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_ERROR, self::ICON_CANONICAL_NAME); 54537748cd8SNickeau } else { 546*4cadd4f8SNickeau LogUtility::msg("The material design icon ($iconName) was downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_INFO, self::ICON_CANONICAL_NAME); 54737748cd8SNickeau } 54837748cd8SNickeau 54937748cd8SNickeau } 55037748cd8SNickeau 55137748cd8SNickeau } 55237748cd8SNickeau 55382a60d03SNickeau private static function getLibraries(): array 554c3437056SNickeau { 55582a60d03SNickeau return array_merge( 55682a60d03SNickeau self::PUBLIC_LIBRARY_ACRONYM, 55782a60d03SNickeau self::DEPRECATED_LIBRARY_ACRONYM 55882a60d03SNickeau ); 55982a60d03SNickeau } 56082a60d03SNickeau 56182a60d03SNickeau /** 56282a60d03SNickeau * @throws ExceptionCombo 56382a60d03SNickeau */ 56482a60d03SNickeau public static function getEmojiCodePoint(string $emojiName) 56582a60d03SNickeau { 566*4cadd4f8SNickeau $path = Site::getComboDictionaryDirectory()->resolve("emojis.json"); 56782a60d03SNickeau $jsonContent = FileSystems::getContent($path); 56882a60d03SNickeau $jsonArray = Json::createFromString($jsonContent)->toArray(); 56982a60d03SNickeau return $jsonArray[$emojiName]; 57082a60d03SNickeau } 57182a60d03SNickeau 572*4cadd4f8SNickeau 57382a60d03SNickeau /** 574*4cadd4f8SNickeau * @param string $iconName 575*4cadd4f8SNickeau * @param string $sep 576*4cadd4f8SNickeau * @return array 57782a60d03SNickeau * @throws ExceptionCombo 57882a60d03SNickeau */ 579*4cadd4f8SNickeau private static function explodeInTwoPartsByLastPosition(string $iconName, string $sep = "-"): array 58082a60d03SNickeau { 581*4cadd4f8SNickeau $index = strrpos($iconName, $sep); 582*4cadd4f8SNickeau if ($index === false) { 583*4cadd4f8SNickeau 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); 58482a60d03SNickeau } 585*4cadd4f8SNickeau $firstPart = substr($iconName, 0, $index); 586*4cadd4f8SNickeau $secondPart = substr($iconName, $index + 1); 587*4cadd4f8SNickeau return [$firstPart, $secondPart]; 58882a60d03SNickeau } 58982a60d03SNickeau 590*4cadd4f8SNickeau 59182a60d03SNickeau /** 59282a60d03SNickeau * @throws ExceptionCombo 59382a60d03SNickeau */ 59482a60d03SNickeau public function render(): string 59582a60d03SNickeau { 59682a60d03SNickeau 597*4cadd4f8SNickeau if (!FileSystems::exists($this->getPath())) { 598*4cadd4f8SNickeau try { 599*4cadd4f8SNickeau $this->download(); 600*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 601*4cadd4f8SNickeau throw new ExceptionCombo("The icon ($this) does not exist and could not be downloaded ({$e->getMessage()}.", self::ICON_CANONICAL_NAME); 602*4cadd4f8SNickeau } 603*4cadd4f8SNickeau } 60482a60d03SNickeau 60582a60d03SNickeau $svgImageLink = SvgImageLink::createMediaLinkFromPath( 60682a60d03SNickeau $this->getPath(), 60782a60d03SNickeau $this->getAttributes() 60882a60d03SNickeau ); 60982a60d03SNickeau return $svgImageLink->renderMediaTag(); 61082a60d03SNickeau 61182a60d03SNickeau 61282a60d03SNickeau } 61382a60d03SNickeau 614*4cadd4f8SNickeau public function __toString() 615*4cadd4f8SNickeau { 616*4cadd4f8SNickeau return $this->getFullQualifiedName(); 617*4cadd4f8SNickeau } 618*4cadd4f8SNickeau 619*4cadd4f8SNickeau private function getLibrary() 620*4cadd4f8SNickeau { 621*4cadd4f8SNickeau return $this->library; 622c3437056SNickeau } 623c3437056SNickeau 62437748cd8SNickeau 62537748cd8SNickeau} 626