xref: /plugin/combo/ComboStrap/Icon.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
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