{"generator":"Code Snippets v3.9.4","date_created":"2026-01-19 15:32","snippets":[{"id":112,"name":"MA Custom Fonts","desc":"<p>Load custom fonts and inject to Oxygen and Bricks.<\/p>\n<p>Version 3.4.5, 2026-01-19\u00a0<br \/>\u00a9 2020-2026, Matthias Altmann<\/p>\n<p>Info: <br \/>en: <a href=\"https:\/\/www.altmann.de\/en\/blog-en\/code-snippet-custom-fonts\/\" target=\"_blank\" rel=\"noopener\">https:\/\/www.altmann.de\/en\/blog-en\/code-snippet-custom-fonts\/<\/a> <br \/>de: <a href=\"https:\/\/www.altmann.de\/blog\/code-snippet-eigene-schriftarten\/\" target=\"_blank\" rel=\"noopener\">https:\/\/www.altmann.de\/blog\/code-snippet-eigene-schriftarten\/<\/a><\/p>","code":"\/*\nPlugin Name:  MA Custom Fonts\nDescription:  Load custom fonts and inject to Gutenberg, Bricks, Oxygen\nAuthor:       <a href=\"https:\/\/www.altmann.de\/\">Matthias Altmann<\/a>\nProject:      Code Snippet MA Custom Fonts\nVersion:      3.4.5\nPlugin URI:   https:\/\/www.altmann.de\/en\/blog-en\/code-snippet-custom-fonts\/\nDescription:  en: https:\/\/www.altmann.de\/en\/blog-en\/code-snippet-custom-fonts\/\n              de: https:\/\/www.altmann.de\/blog\/code-snippet-eigene-schriftarten\/\nCopyright:    \u00a9 2020-2026, Matthias Altmann\n\nTESTED WITH:\nProduct\t\tVersions\n--------------------------------------------------------------------------------------------------------------\nPHP \t\t... 8.3\nWordPress\t... 6.9\nBricks\t\t... 2.1.4\nOxygen\t\t... 4.9.5\n--------------------------------------------------------------------------------------------------------------\n\nVERSION HISTORY:\nDate\t\tVersion\t\tDescription\n--------------------------------------------------------------------------------------------------------------\n2026-01-19 \t3.4.5\t\tCompatibility:\n\t\t\t\t\t\t- Core Framework 1.9.3 added a toggle \"Disable Fonts Integration\". \n\t\t\t\t\t\t  Disabling Fonts Integration allows the use of MA Custom Fonts with Oxygen Builder.\n\t\t\t\t\t\t  (Thanks to Luke Allen from the Core Framework dev team for adding this toggle and \n\t\t\t\t\t\t  providing a related compatibility check code sample.)\n2025-08-29\t3.4.4\t\tFixes:\n\t\t\t\t\t\t- Fixed font base URL for WP setup in subdirectory\n\t\t\t\t\t\t  (Thanks to Salomon van Riesen and Chanan Strauss for reporting)\n\t\t\t\t\t\tChanges:\n2025-06-08\t3.4.3\t\tNew Feature Preview:\n\t\t\t\t\t\t- Initial inofficial support for WordPress FSE Site Editor (Full Site Editing) themes. \n\t\t\t\t\t\t  Custom fonts are now available in the Site Editor, too.\n\t\t\t\t\t\t  The custom fonts are listed in the \"Typography\" section of the \"Styles\" panel.\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Font URL pattern in ma-customfonts.css can now be configured to use \n\t\t\t\t\t\t  'relative', 'full' or 'scheme-less' URLs. Default is 'relative'.\n\t\t\t\t\t\t  This improves compatibility with CDNs, 3rd party CSS optimizers\/minimizers,  and some \n\t\t\t\t\t\t  polyfills.\n\t\t\t\t\t\t- Implemented QueryMonitor timing\n\t\t\t\t\t\t- Added \"Core Framework\" to incompatibilities if installed together with Oxygen Builder\n2024-03-15\t3.4.2\t\tFixes:\n\t\t\t\t\t\t- Fixed PHP warning in admin_init() line 818: $_REQUEST['page'] might not be defined \n\t\t\t\t\t\t  when using Bricks.\n2024-03-04\t3.4.1\t\tChanges:\n\t\t\t\t\t\t- Builder fonts in Gutenberg: Call of Oxygen function ct_get_global_settings embedded\n\t\t\t\t\t\t  in call_user_func() to avoid being marked as unknown in Bricks setups.\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Fixed PHP warning in admin_init(): Variable $builder_fonts_to_gutenberg definition \n\t\t\t\t\t\t  was inside of Oxygen block, hence undefined in Bricks block.\n2024-02-25\t3.4.0\t\tReorganization of code base.\n\t\t\t\t\t\tNew Features:\n\t\t\t\t\t\t- Support for assigning fonts in Gutenberg. \n\t\t\t\t\t\t  Can be disabled by setting $gutenberg_font_family_select = false;\n\t\t\t\t\t\t- Oxygen Builder: Hide \"Top 20\" Google Fonts if Google Fonts are disabled.\n\t\t\t\t\t\t- Added filter for excluding ma-customfonts.css from FlyingPress CSS minimizer\n\t\t\t\t\t\t  which causes issues with already minimized CSS containing @font-face rules.\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Changed CSS file name from ma_customfonts.css to ma-customfonts.css\n\t\t\t\t\t\t- Changed test page slug from ma_customfonts to ma-customfonts\n\t\t\t\t\t\t- Removed setting $recursive for font file scan. Always scan recursive.\n\t\t\t\t\t\t- Added compatibility check for \"Oxy Font Manager\"\n\t\t\t\t\t\t- Oxygen Builder font lists now show \"Custom\" badge for injected fonts\n\t\t\t\t\t\t- Custom Fonts Test now loads CSS using URL parameter ver= to assure proper refresh.\n\t\t\t\t\t\t- Setting $fonts_in_gutenberg changed to $builder_fonts_in_gutenberg (Bricks, Oxygen)\n\t\t\t\t\t\t- Setting renamed: \n\t\t\t\t\t\t  $cssoutput to $css_output, $cssminimize to $css_minimize, $fontdisplay to $font_display\n\t\t\t\t\t\t- $timing now allows levels 0\/false (off), 1\/true (basic), 2 (extended)\n\t\t\t\t\t\t- $timing now also calculates memory usage. Please note that memory usage won't simply \n\t\t\t\t\t\t  be cummulated. Methods use memory temporarily but also free memory after use.\n2022-10-17\t3.3.1\t\tChanges:\n\t\t\t\t\t\t- Migrated JavaScript from jQuery to vanilla JS (ES6) to eliminate jQuery dependency.\n\t\t\t\t\t\t- Modernized initialization to avoid errors with WPCodeBox:\n\t\t\t\t\t\t  Replaced @ error control operator with ?? null coalescing operator\n\t\t\t\t\t\t  (Thanks to Alexander van Aken for reporting)\n2022-09-26\t3.3.0\t\tNew Features:\n\t\t\t\t\t\t- Custom Fonts are now also available in Bricks Builder. They are listed in section\n\t\t\t\t\t\t  \"Standard Fonts\".\n\t\t\t\t\t\t  (Thanks to Luke Wakefield for the implementation idea and to Tom Homer for contacting\n\t\t\t\t\t\t  and informing me about Luke's solution)\n\t\t\t\t\t\t- New configuration option $fonts_in_gutenberg:\n\t\t\t\t\t\t  Allows to enable (default) or disable the assignment of custom fonts in Gutenberg. \n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Renamed code snippet from \"MA Oxygen Custom Fonts\" to \"MA Custom Fonts\"\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- The snippet applies display and text fonts defined in Oxygen to Gutenberg Editor. \n\t\t\t\t\t\t  Google fonts defined in Oxygen are not loaded for Gutenberg. \n\t\t\t\t\t\t  So we can't assign Google fonts to Gutenberg. Only custom fonts are now assigned.\n\t\t\t\t\t\t  (Thanks to Kamil Alhaijali for reporting) \n2022-09-19\t3.2.7\t\tCompatibility Fix:\n\t\t\t\t\t\t- In Oxygen 4.0.4, Pro Menu calls ECF_Plugin::get_font_families() as Ajax call. \n\t\t\t\t\t\t  For performance reasons, MA Custom Fonts doesn't get initialized for Ajax calls and \n\t\t\t\t\t\t  doesn't have the base font directory set, which causes an error. We now just return \n\t\t\t\t\t\t  an empty custom fonts list to prevent this error. That doesn't impact the Pro Menu \n\t\t\t\t\t\t  functionality. \n\t\t\t\t\t\t  (Many thanks to Kevin Pudlo from the Oxygen development team for reporting, support \n\t\t\t\t\t\t  and testing concerning the Pro Menu component, and Alexander van Aken for reporting \n\t\t\t\t\t\t  a similar issue concerning the Mega Menu component.)\n2022-06-02\t3.2.6\t\tNew Features:\n\t\t\t\t\t\t- New configuration option $wfl_support_woff: \n\t\t\t\t\t\t  Font packages downloaded from Web Font Loader (https:\/\/webfontloader.altmann.de) \n\t\t\t\t\t\t  contain WOFF2 files supported by all modern browsers, and also WOFF files to support \n\t\t\t\t\t\t  ancient browsers before 2016 like Internet Explorer, or Safari on older Apple devices.\n\t\t\t\t\t\t  The new default setting is to NOT provide old style WOFF files for ancient browsers.\n\t\t\t\t\t\t  Set this option to true if you still need to support old browsers before 2016.\n\t\t\t\t\t\t  (Many thanks to Sunny Trochaniak and Yan Kiara for reporting issues and supporting \n\t\t\t\t\t\t  investigations with unexpected WOFF loading when using symbols\/emoji)\n\t\t\t\t\t\t- Added display of timing details at end of page Appearance > MA Custom Fonts\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Corrected initialization of dummy ECF plugin to let Oxygen detect custom fonts and \n\t\t\t\t\t\t  prevents attempts to be loaded as Google fonts\n\t\t\t\t\t\t  (Thanks to Firat Sekerli for reporting)\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Removed configuration and all code for debugging.\n\t\t\t\t\t\t- For Web Font Loader fonts: Only emit CSS if related font file exists. \n\t\t\t\t\t\t  User might have deleted e.g. the files for a specific language.\n2022-03-13\t3.2.5\t\tNew Features:\n\t\t\t\t\t\t- Legend and coloring for font formats in custom fonts test screen\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Renamed Admin menu Appearance > \"Custom Fonts\" to \"MA Custom Fonts\"\n\t\t\t\t\t\t- Renamed test shortcode from \"maltmann_custom_fonts_test\" to \"ma-customfonts-test\"\n\t\t\t\t\t\t- Optimized handling of font_base (dir, url) by class var\n\t\t\t\t\t\t- Enhanced detection of variable weight fonts by [wght] in filename\n\t\t\t\t\t\t  (Thanks to Paul Batey for reporting)\n\t\t\t\t\t\t- Adapted min\/max font weight for variable fonts from 100\/900 to 1\/1000 according to \n\t\t\t\t\t\t  https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@font-face\/font-weight#values\n\t\t\t\t\t\t  (Thanks to Paul Batey for reporting)\n\t\t\t\t\t\t- Completely re-built custom font test screen (WP Admin and Shortcode) to improve \n\t\t\t\t\t\t\t- variable weight fonts (display logic)\n\t\t\t\t\t\t\t- responsive view (for test on smartsphones)\n2022-02-11\t\t\t\tTested with PHP 8.0, WordPress 5.9, Oxygen 4.0 beta 1\n2021-10-26\t3.2.4\t\tChanges:\n\t\t\t\t\t\t- Deferred initialization to hook wp_loaded, after incompatibility checks\n\t\t\t\t\t\t- Gutenberg: Apply custom fonts for new posts also\n\t\t\t\t\t\t- Renamed IDs (for scripts and styles) and CSS classes (for font test) for consistency\n\t\t\t\t\t\tPerformance:\n\t\t\t\t\t\t- Avoid unneccessary init by separating admin\/frontend and more detailed checks \n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Fixed an issue comparing hashes for code and file\n\t\t\t\t\t\t  (Thanks to David Sitniansky for reporting)\n\t\t\t\t\t\t- Emit styles for Gutenberg correctly if $cssoutput is configured as 'html'\n2021-10-15\t3.2.3\t\tChanges:\n\t\t\t\t\t\t- Gutenberg: Use display font for post title\n\t\t\t\t\t\t  (Thanks to Sunny Trochaniak for reporting)\n\t\t\t\t\t\t- Fonts preview: Changed sample text size from 15 to 16 px which is browser standard \n\t\t\t\t\t\t- Fonts preview: Shortcode output uses WP system fonts for UI instead of custom fonts\n\t\t\t\t\t\t- CSS file link now contains ?ver=... instead of ?hash only\n\t\t\t\t\t\tPerformance: \n\t\t\t\t\t\t- Only create CSS file if contents (font configuration) have changed \n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Removed itemprop=\"stylesheet\" from <link rel=\"stylesheet\" ...>\n\t\t\t\t\t\t  (Thanks to Max Gottschalk for reporting and testing)\n\t\t\t\t\t\t- Proper quoting for font families\n2021-08-02\t3.2.2\t\tChanges:\n\t\t\t\t\t\t- Using scheme-less URL to avoid issues with wrong WordPress URL configuration\n\t\t\t\t\t\t- Added admin notice if folder wp-content\/uploads\/fonts is not writable.\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Fixed issue with uppercase font file extensions.\n2021-06-18\t3.2.1\t\tFixes: \n\t\t\t\t\t\t- Fixed typo in CSS for Gutenberg\n2021-06-18\t3.2.0\t\tNew Features:\n\t\t\t\t\t\t- Display Custom Fonts in Gutenberg (enqueue ma_customfonts.css for font definitions, \n\t\t\t\t\t\t  add custom style for display and text font from Oxygen global settings)\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Auto-create folder \/wp-content\/uploads\/fonts\n2021-05-17\t3.1.3\t\tChanges:\n\t\t\t\t\t\t- Optimized init sequence\n\t\t\t\t\t\t- Emit implementation and version in CSS\n\t\t\t\t\t\t- Reversed Version History order\n2021-05-16\t3.1.2\t\tChanges:\n\t\t\t\t\t\t- Avoid font swap: Load ma-customfonts.css early; default font-display now \"block\"\n\t\t\t\t\t\tNew Features:\n\t\t\t\t\t\t- Allow space in addition to dashes to detect font weights and styles\n\t\t\t\t\t\t  (Thanks to Henning Wechsler for reporting)\n2021-03-21\t3.1.1\t\tFixes:\n\t\t\t\t\t\t- Fixed font loading in Gutenberg editor (with Oxygen Gutenberg Integration)\n2021-03-20\t3.1.0\t\tNew Features:\n\t\t\t\t\t\t- \"Oblique\" in font file name is now detected as italic style\n\t\t\t\t\t\t- Custom Fonts test: Option to show font weights\/styles without files as browser would \n\t\t\t\t\t\t  simulate. \n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Output Custom Font CSS in head instead of footer to prevent font swap\n\t\t\t\t\t\t- Custom Fonts test: Changed logic for output font samples and related file info\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Custom Fonts test: Fixed font file count for fonts provided by Web Font Loader\n2021-03-08\t3.0.2\t\tFix:\n\t\t\t\t\t\t- Compatibility with Windows server and local dev environments.\n\t\t\t\t\t\t  (Thanks to Franz M\u00fcller for reporting and testing)\n2021-02-23\t3.0.1\t\tFixes:\n\t\t\t\t\t\t- Compatibility with WordPress 5.6.2 (doesn't set REQUEST::action anymore)\n\t\t\t\t\t\t- Compatibility check with Swiss Knife's Font Manager feature\n\t\t\t\t\t\t- Compatibility with Swiss Knife (font lists did not display custom fonts light blue)\n2021-02-18\t3.0.0\t\tNew Features:\n\t\t\t\t\t\t- Support for font packages from Web Font Loader (https:\/\/webfontloader.altmann.de\/)\n\t\t\t\t\t\t- New configuration option: CSS output as inline CSS or external CSS file (cacheable)\n\t\t\t\t\t\t- New configuration option: CSS minimize (was controlled by debug switch before)\n\t\t\t\t\t\t- Changed configuration option: font-display may now be specified as desired, \n\t\t\t\t\t\t  default is now 'auto'\n2021-01-24 \t2.5.2\t\tNew Features:\n\t\t\t\t\t\t- Custom Fonts test (via Admin panel and shortcode) now allows custom sample text\n2021-01-23\t2.5.1\t\tFix:\n\t\t\t\t\t\t- Changed compatibility check process: \n\t\t\t\t\t\t  Changed Hook for plugin compatibility check from plugins_loaded to init\n\t\t\t\t\t\t  Check only if admin and function is_plugin_active exists\n\t\t\t\t\t\t  (Thanks to Sebastian Albert for reporting and testing)\n2021-01-23\t2.5.0\t\tNew features:\n\t\t\t\t\t\t- WP Admin Menu: Appearance > Custom Fonts \n\t\t\t\t\t\t  Shows a list of all registered custom fonts, including samples, weights, formats\n\t\t\t\t\t\t  with adaptable sample font size \n\t\t\t\t\t\t- Detect font weight terms \"Book\" (400) and \"Demi\" (600) \n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Redesign of classes (MA_CustomFonts, ECF_Plugin)\n\t\t\t\t\t\t- Font swap is now a configuration option\n\t\t\t\t\t\t- Cut \"-webfont\" from font name\n2020-12-08\t2.2.5\t\tChanges:\n\t\t\t\t\t\t- In CSS, font sources are now listed in a prioritized order (eot,woff2,woff,ttf,otf,svg)\n\t\t\t\t\t\t  (Thanks to Viorel Cosmin Miron for reporting)\n\t\t\t\t\t\t- Test shortcode now also displays available font formats\n2020-11-27\t2.2.4\t\tFix:\n\t\t\t\t\t\t- Corrected typo in variable name (2 occurrences) that could cause repeated search \n\t\t\t\t\t\t  for font files. (Thanks to Viorel Cosmin Miron for reporting)\n2020-11-25\t2.2.3\t\tChanges:\n\t\t\t\t\t\t- In Oxygen font selectors the custom fonts are now displayed in lightblue \n\t\t\t\t\t\t  to distinguish from default, websafe and Google Fonts \n2020-11-25\t2.2.2\t\tNew features:\n\t\t\t\t\t\t- Partial support for fonts with variable weights, detected by \"VariableFont\" in \n\t\t\t\t\t\t  filename. CSS output as font-weight:100 900;\n2020-11-24\t2.2.1\t\tNew features:\n\t\t\t\t\t\t- Shortcode [ maltmann_custom_font_test ] for listing all custom fonts with their weights \n\t\t\t\t\t\t  and styles\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Fonts are now sorted alphabetically for e.g. CSS output\n\t\t\t\t\t\t- Added more request rules to skipping code execution when not needed\n2020-11-23\t2.2.0\t\tNew features:\n\t\t\t\t\t\t- Detection of font weight from number values \n\t\t\t\t\t\t- CSS now contains font-display:swap;\n2020-10-03 \t2.1.1\t\tFix:\n\t\t\t\t\t\t- Handle empty fonts folder correctly. \n\t\t\t\t\t\t  (Thanks to Mario Peischl for reporting)\n\t\t\t\t\t\t- Corrected title and file name (typo \"cutsom\") of Code Snippet\n2020-09-16\t2.1.0\t\tNew features:\n\t\t\t\t\t\t- Detection of font weight and style from file name\n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- EOT: Typo in extension detection\n\t\t\t\t\t\t- EOT: Missing quote in style output\n2020-09-15\t2.0.0\t\tImproved version\n\t\t\t\t\t\t- Finds all font files (eot, otf, svg, ttf, woff, woff2) in directory wp-content\/uploads\/fonts\/\n\t\t\t\t\t\t- Optionally recursive\n\t\t\t\t\t\t- Takes font name from file name\n\t\t\t\t\t\t- Emits optimized CSS with alternative font formats\n\t\t\t\t\t\t- Special handling for EOT for Internet Explorer\n2020-04-10\t1.0.0\t\tInitial version for client project\n--------------------------------------------------------------------------------------------------------------\n*\/\n\nif (!class_exists('MA_CustomFonts')) :\nclass MA_CustomFonts {\n\n\tconst TITLE\t\t\t= 'MA Custom Fonts';\n\tconst SLUG\t\t\t= 'ma-customfonts';\n\tconst VERSION\t\t= '3.4.5';\n\n\t\/\/ ===== CONFIGURATION ==============================================================================================\n\t\/** Define how fonts are displayed. \n\t * Corresponds to CSS font-display: 'auto', 'block', 'swap', 'fallback', 'optional' or '' (disable) \n\t * @link https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@font-face\/font-display\n\t *\/\n\tpublic $font_display\t\t\t\t\t= 'block';\n\n\t\/** Defines how CSS is emitted: \n\t * - 'file' emits a link to a CSS file (cacheable by browser), \n\t * - 'html' emits inline CSS in HTML header \n\t *\/\n\tpublic $css_output\t\t\t\t\t\t= 'file';\n\t\n\t\/** Enables (`true`) or disables (`false`) minimizing of CSS. \n\t * Caution! Some Performance\/Cache plugins don't handle minimized CSS containing @font-face correctly. \n\t * For FlyingPress, there's now already a filter to skip already minimized ma_customfonts.css.  \n\t *\/\n\tpublic $css_minimize\t\t\t\t\t= true;\n\n\t\/** Define the URL pattern used in the CSS file \"ma-customfonts.css\".\n\t * relative: \tRelative URLs, e.g. \/wp-content\/uploads\/fonts\/xyz.woff2. This is the new default.\n\t * full: \t\tFull URLs, e.g. https:\/\/example.com\/wp-content\/uploads\/fonts\/xyz.woff2\n\t * scheme-less:\tScheme-less URLs, e.g. \/\/example.com\/wp-content\/uploads\/fonts\/xyz.woff2\n\t * Note:\n\t * Scheme-less URLs don't have the http: or https: protocol prefix, but just start with \/\/.\n\t * With this pattern, the browser automatically chooses the protocol according to the current page, \n\t * which makes it fool-proof for sites with or without SSL. \n\t * This was the default in previous versions of the snippet.\n\t * However, some 3rd party solutions like CSS optimizers\/minimizers or polyfills don't handle \n\t * scheme-less URLs correctly and will cause issues with loading the font files.\n\t *\/\n\t\/\/ since 3.4.3\n\tpublic $css_font_urls\t\t\t\t\t= 'relative'; \/\/ 'relative' (default), 'full', 'scheme-less'\n\t\n\t\/** Defines support of old style WOFF files from Web Font Loader for browsers before 2016. *\/\n\tpublic $wfl_support_woff\t\t\t\t= false;\t\n\n\t\/** Sample text used on admin page Appearance > MA Custom Fonts *\/\n\tpublic $sample_text \t\t\t\t\t= 'The quick brown fox jumps over the lazy dog.';\t\n\n\t\/** Gutenberg: Enables selection of available custom font families *\/\n\tpublic $gutenberg_font_family_select \t= true; \n\t\n\t\/** Bricks\/Oxygen Builder: \n\t * Enables use of default fonts in Gutenberg as defined in Theme Styles (Bricks) or Global Settings (Oxygen). \n\t * If $gutenberg_font_family_select is set to true, this setting is ignored since fonts need to be loaded anyway.\n\t *\/\n\tpublic $builder_fonts_in_gutenberg\t\t= true;\t\n\n\t\/** Enable runtime and memory usage info to WordPress debug.log if WP_DEBUG also enabled. \n\t * Please note that memory usage won't simply be cummulated. Methods use memory temporarily \n\t * but also free memory after use. Memory usage is thus just a snapshot.\n\t * - false\/0: Disabled \n\t * - true\/1: Enabled \n\t * - 2: Extended.\n\t *\/\n\tpublic $timing\t\t\t\t\t\t\t= 0;\n\n\t\/\/ ===== INTERNAL. DO NOT EDIT. =====================================================================================\n\tprivate $incompatibilities \t\t\t\t= [];\t\/\/ incompatibilities detected before initialization\n\tprivate $prioritized_formats\t\t\t= ['eot','woff2','woff','ttf','otf','svg']; \/\/ font formats in prioritized CSS order\n\tprivate $var_weight_formats\t\t\t\t= ['woff2','ttf']; \/\/ font formats allowing variable weight\n\tprivate $fonts_base\t\t\t\t\t\t= null; \/\/ will be initialized automatically\n\tprivate $fonts_details_cache\t\t\t= [];\t\/\/ cache for already parsed font details\n\tprivate $fonts \t\t\t\t\t\t\t= null;\t\/\/ list of fonts and related files\n\tprivate $font_families\t\t\t\t\t= null;\t\/\/ list of font family names\n\tprivate $font_files_cnt\t\t\t\t\t= 0;\t\/\/ number of font files parsed\n\tprivate $font_css\t\t\t\t\t\t= null;\t\/\/ temp storage for custom font css\n\tprivate $gutenberg_font_classes\t\t\t= null; \/\/ css classes to be used with Gutenberg (.has-xyz-font-family)\n\tprivate $gutenberg_editor_font_styles\t= [];\t\/\/ temp storage for Gutenberg css defining oxygen fonts\n\n\tprivate $timing_collect_fonts\t\t\t= 0;\t\/\/ used to show timing on admin page\n\tprivate $timing_generate_css\t\t\t= 0;\t\/\/ used to show timing on admin page\n\tprivate $timing_total_runtime\t\t\t= 0;\t\/\/ total runtime\n\n\tprivate $memory_collect_fonts\t\t\t= 0;\t\/\/ used to show memory usage on admin page\n\tprivate $memory_generate_css\t\t\t= 0;\t\/\/ used to show memory usage on admin page\n\tprivate $memory_total_usage\t\t\t\t= 0;\t\/\/ total memory usage\n\n\t\n\n\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Class constructor\n\t *\/\n\tfunction __construct() {\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t$GLOBALS['MA_CustomFonts'] = $this;\n\n\t\tif (wp_doing_ajax()) {\n\t\t\t$run_ajax = false; \/\/ don't run for AJAX requests, except...\n\t\t\tif (strpos($_REQUEST['action']??'','macfm')===0) $run_ajax = true; \/\/ allow font manager\n\t\t\tif (!$run_ajax) goto DONE;\n\t\t}\t\n\t\tif (wp_doing_cron()) \t\tgoto DONE;\t\/\/ don't run for CRON requests\n\t\tif (wp_is_json_request()) \tgoto DONE;\t\/\/ don't run for JSON requests\n\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s Initializing...',__CLASS__));}\n\n\t\tif (!defined('MA_CustomFonts_Version')) define('MA_CustomFonts_Version',self::VERSION);\n\n\t\tif ($this->is_incompatible()) return;\n\n\t\t$this->fonts_base = $this->get_fonts_base(); \n\t\tif (!$this->fonts_base) {return;}\n\n\t\t\/\/ collect fonts, font families, font classes gutenberg\n\t\t$this->collect_fonts();\n\n\t\t\/\/ pre-fill custom font css, and optionally write file\n\t\t$this->font_css = $this->generate_font_css();\n\n\t\t\/\/ FlyingPress doesn't handle CSS minify correctly for already minified CSS containing @font-face\n\t\tadd_filter('flying_press_exclude_from_minify:css', function ($exclude_keywords) \n\t\t\t{return array_merge($exclude_keywords??[],[self::SLUG.'.css']);});\n\t\t\n\t\t\/\/ Backend: Admin menu, \n\t\tadd_action('admin_menu', [$this, 'admin_init_menu']);\n\t\tadd_action('admin_init', [$this, 'admin_init']);\n\n\t\t\/\/ Frontend: Emit custom font css in head \n\t\tadd_action('wp_head', [$this, 'frontend_css'], 5);\n\t\tadd_action('wp_footer', [$this, 'debug_info']);\n\n\t\t\/\/ Bricks: Add custom fonts to Bricks Builder UI.\n\t\tadd_filter( 'bricks\/builder\/standard_fonts', function($fonts) {\n\t\t\treturn array_merge($fonts, $this->get_font_families());\n\t\t});\n\n\t\t\/\/ Gutenberg\n\t\tif ($this->gutenberg_font_family_select) {\n\t\t\tadd_filter('wp_theme_json_data_theme', [$this, 'theme_json_inject_fonts']);\n\t\t\tadd_action('admin_head', [$this, 'blocks_config']);\n\t\t}\n\n\t\t\/\/ Shortcode for testing custom fonts (listing all fonts with their formats, weights, styles)\n\t\tadd_shortcode('ma-customfonts-test', function( $atts, $content, $shortcode_tag ) {\n\t\t\treturn $this->get_font_samples('shortcode');\n\t\t}); \n\n\t\tDONE:\n\t\t\/\/ add a handler for logging total runtime\n\t\tadd_action('shutdown', [$this, 'total_runtime_memory']);\n\t\t\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1 && $this->fonts_base) {error_log(sprintf('%s Initialization runtime %.5f sec., memory usage: %.2f KB',__CLASS__,$et-$st, ($em-$sm)\/1024));}\n\t\t$this->timing_total_runtime += $et-$st;\n\t\t$this->memory_total_usage += $em-$sm;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Logs total runtime. (only if initialized)\n\t *\/\n\tpublic function total_runtime_memory(){\n\t\tif (WP_DEBUG && $this->timing && $this->fonts_base) {error_log(sprintf('%s Total runtime: %.5f sec., memory usage: %.2f KB', __CLASS__, $this->timing_total_runtime, $this->memory_total_usage\/1024));}\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Checks for incompatibilities. Registers admin notice.\n\t * @return bool \t\t\t\t`true` if any incompatibilities found\n\t *\/\n\tprivate function is_incompatible(): bool{\n\t\tif (!function_exists('is_plugin_active')) return false;\n\t\t$incomp = [];\n\t\t\/*\n\t\t@since 3.4.3: Core Framework together with Oxygen Builder is incompatible\n\t\t@since 3.4.5: Core Framework 1.9.3 added a \"Disable Fonts Integration\" toggle. With this enabled,\n\t\tthe snippet is compatible and can be used.\n\t\t*\/\n\t\tif (is_plugin_active('core-framework\/core-framework.php') && is_plugin_active('oxygen\/functions.php')) {\n\t\t\t$cf_version = defined('CORE_FRAMEWORK_VERSION') ? constant('CORE_FRAMEWORK_VERSION') : '0.0.0';\n\t\t\tif (version_compare($cf_version, '1.9.3', '>=')) {\n\t\t\t\t\/\/ check if fonts integration is disabled\n\t  \t\t\t$cf_options = get_option('core_framework_main', []);\n\t\t\t\tif (!($cf_options['disable_fonts'] ?? false)) {\n\t  \t\t\t\t$incomp[] = '\"Core Framework\" (version 1.9.3 or higher with Fonts Integration enabled) when used with \"Oxygen Builder\"';\n  \t\t\t\t}\n\t\t\t} else {\n\t\t\t\t$incomp[] = '\"Core Framework\" (version up to 1.9.2) when used with \"Oxygen Builder\"';\n\t\t\t}\n\t\t}\n\t\tif (is_plugin_active('elegant-custom-fonts\/elegant-custom-fonts.php')) {\n\t\t\t$incomp[] = '\"Elegant Custom Fonts\"';\n\t\t}\n\t\tif (is_plugin_active('Oxy-Font-Manager\/Oxy-Font-Manager.php')) {\n\t\t\t$incomp[] = '\"Oxy Font Manager\"';\n\t\t}\n\t\tif (is_plugin_active('swiss-knife\/swiss-knife.php') && (get_option('swiss_font_manager')=='yes')) {\n\t\t\t$incomp[] = '\"Swiss Knife\" with feature \"Font Manager\" enabled';\n\t\t}\n\t\tif (is_plugin_active('use-any-font\/use-any-font.php')) {\n\t\t\t$incomp[] = '\"Use Any Font\"';\n\t\t}\n\t\t$this->incompatibilities = $incomp;\n\t\tif (count($incomp) && is_admin()) {\n\t\t\tadd_action('admin_notices', function(){\n\t\t\t\tif (WP_DEBUG ) {error_log(self::TITLE.' Incompatibilities: '.implode(', ',$this->incompatibilities));}\n\t\t\t\techo '<div class=\"notice notice-error is-dismissible\">\n\t\t\t\t\t\t<p>The '.$this->get_script_details()->full.' is not compatible with the Plugin '.implode(', ',$this->incompatibilities).'.<br\/>\n\t\t\t\t\t\tPlease deactivate either \"'.self::TITLE.'\" or the named Plugin (feature).<\/p>\n\t\t\t\t\t<\/div>';\n\t\t\t});\n\t\t}\t\n\t\treturn count($incomp) ? true : false;\t\n\t}\n\n\t\/\/===================================================================================================================\n\t\/\/ FONTS\n\t\/\/===================================================================================================================\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/** \n\t * Returns base dir and url for fonts. Creates directory wp-content\/uploads\/fonts if necessary.\n\t * @return null|object \t\t\tThe object with properties `dir`, `url`. Returns `null` on error.\n\t *\/\n\tpublic function get_fonts_base(): ?object{\n\t\t$retval = (object)['dir'=>null, 'url'=>''];\n\t\t$fonts_dir_info = wp_get_upload_dir();\n\t\t$retval->dir = $fonts_dir_info['basedir'].'\/fonts';\n\t\t$retval->url = $fonts_dir_info['baseurl'].'\/fonts';\n\t\t$retval->url_full = $retval->url; \/\/ keep original full URL, e.g. for inclusing ma-customfonts.css\n\t\t\/\/ create fonts folder if not exists\n\t\tif (!file_exists($retval->dir)) {\n\t\t\tif (!@mkdir($retval->dir)) {\n\t\t\t\tadd_action('admin_notices', function(){\n\t\t\t\t\techo '<div class=\"notice notice-error\"><p>['.self::TITLE.'] Error creating fonts base folder <code>wp-content\/uploads\/fonts<\/code>.<\/p><\/div>';\n\t\t\t\t});\n\t\t\t\terror_log(sprintf('%s::%s() Error creating fonts base folder.', __CLASS__, __FUNCTION__)); \n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tif (!is_writable($retval->dir)) {\n\t\t\tadd_action('admin_notices', function(){\n\t\t\t\techo '<div class=\"notice notice-error\"><p>['.self::TITLE.'] Folder <code>wp-content\/uploads\/fonts<\/code> is not writable. Please correct folder permissions.<\/p><\/div>';\n\t\t\t});\n\t\t}\n\t\t\/\/ @since 3.4.3: URL style can now be configured\n\t\tswitch($this->css_font_urls) {\n\t\t\tcase 'scheme-less':\n\t\t\t\t\/\/ scheme-less URLs, e.g. \/\/example.com\/wp-content\/uploads\/fonts\n\t\t\t\t$retval->url = preg_replace('\/^https?\\:\/','',$retval->url);\n\t\t\t\tbreak;\n\t\t\tcase 'full': break; \/\/ no change, keep full URL\n\t\t\tcase 'relative': \/\/ use default\n\t\t\tdefault: \/\/ relative URLs, e.g. \/wp-content\/uploads\/fonts or \/_sub\/wp-content\/uploads\/fonts\n\t\t\t\t\/\/ get site URL\n\t\t\t\t$site_url = get_site_url();\n\t\t\t\t\/\/ detect any subdirectory in site URL\n\t\t\t\t$subdirectory = parse_url($site_url, PHP_URL_PATH);\n\t\t\t\t\/\/ remove site URL from font URL, but leave subdirectory\n\t\t\t\t$retval->url = str_replace($site_url, $subdirectory??'', $retval->url);\n\t\t\t\tbreak;\n\t\t}\n\n\t\t\/\/ V3.4.0: Rename ma_customfonts.css to ma-customfonts.css\n\t\tif (file_exists($retval->dir.'\/ma_customfonts.css')) {\n\t\t\t$status = copy($retval->dir.'\/ma_customfonts.css', $retval->dir.'\/'.self::SLUG.'.css');\n\t\t\tif ($status === true) {unlink($retval->dir.'\/ma_customfonts.css');}\n\t\t}\n\t\treturn $retval;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns slug for a font family name.\n\t * @param string $font_name\t\tThe font name\n\t * @return string\t\t\t\tThe slug for the font name\n\t *\/\n\tprivate function get_font_slug(string $font_name): string {\n\t\t$retval = preg_replace('\/[^a-z0-9\\-\\_]\/','-',strtolower($font_name));\n\t\treturn $retval;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns a list of font family names. \n\t * @return array\t\t\t\tThe list of font family names.\n\t *\/\n\tfunction get_font_families(): array {\n\t\treturn $this->font_families??[];\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns the internal fonts list. Used by MA Custom Fonts Manager\n\t * @return array\t\t\t\tThe list of fonts.\n\t *\/\n\tfunction get_fonts(): array {\n\t\treturn $this->fonts??[];\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * - Collects font files from font folder. \n\t * - Stores family names to $font_families. \n\t * - Prepares Gutenberg CSS Classes in $gutenberg_font_classes.\n\t *\/\n\tprivate function collect_fonts() {\n\t\tdo_action('qm\/start', __METHOD__); \/\/ Query Monitor Profiling\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\tif (isset($this->fonts)) return; \/\/ Already collected.\n\t\t$this->fonts = [];\n\t\t\/\/ recursive scan for font files (including subdirectories)\n\t\t$directory_iterator = new RecursiveDirectoryIterator($this->fonts_base->dir,  RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::UNIX_PATHS);\n\t\t$file_iterator = new RecursiveIteratorIterator($directory_iterator);\n\t\t\/\/ loop through files and collect font and JSON files\n\t\t$font_splfiles = [];\n\t\t$json_splfiles = [];\n\t\tforeach( $file_iterator as $file) {\n\t\t\t\/\/ V3: A JSON file might be available from Web Font Loader\n\t\t\tif ($file->getExtension() == 'json') {\n\t\t\t\t$json_splfiles[] = $file;\n\t\t\t}\n\t\t\tif (in_array(strtolower($file->getExtension()), $this->prioritized_formats)) {\n\t\t\t\t$font_splfiles[] = $file;\n\t\t\t}\n\t\t}\n\t\t\n\t\t\/\/ V3: check JSON files. If it defines \"family\" read the font name and CSS\n\t\t$json_font_families = [];\n\t\tforeach ($json_splfiles as $json_splfile) {\n\t\t\tif ($font_details = @json_decode(@file_get_contents($json_splfile->getPathname()))) {\n\t\t\t\t\/\/ It's a JSON from Web Font Loader?\n\t\t\t\tif (isset($font_details->creator) && (strpos($font_details->creator, 'Web Font Loader')=== 0)) {\n\t\t\t\t\t\/\/ store font family name \n\t\t\t\t\t$json_font_families[$json_splfile->getBasename('.json')] = $font_details->family;\n\t\t\t\t\t\/\/ drop all collected font files for that font since they are listed in JSON file\n\t\t\t\t\t$font_path = $json_splfile->getPath().'\/';\n\t\t\t\t\tforeach ($font_splfiles as $idx => $font_splfile) {\n\t\t\t\t\t\tif (strpos($font_splfile->getPath().'\/',$font_path) === 0) {\n\t\t\t\t\t\t\t$this->font_files_cnt ++;\n\t\t\t\t\t\t\tunset($font_splfiles[$idx]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\/\/ get the relative path\n\t\t\t\t\t$font_path_relative = str_replace($this->fonts_base->dir,'',$font_path);\n\t\t\t\t\t\/\/ encode every single path element since we might have spaces or special chars \n\t\t\t\t\t$font_path_url = implode('\/',array_map('rawurlencode',explode('\/',$font_path_relative)));\n\t\t\t\t\t\n\t\t\t\t\t\/\/ add CSS blocks (could be multiple unicode ranges) to fonts list\n\t\t\t\t\t$font_baseurl = $this->fonts_base->url . $font_path_url;\n\t\t\t\t\tforeach ($font_details->css as $css_ruleset) {\n\t\t\t\t\t\t\/\/ check for WOFF support. Skip if not enabled\n\t\t\t\t\t\tif ($css_ruleset->format=='woff' && !$this->wfl_support_woff) {continue;}\n\t\t\t\t\t\t\/\/ check if file exists. User might have deleted it.\n\t\t\t\t\t\tif (!file_exists($font_path . $css_ruleset->url)) {continue;}\n\t\t\t\t\t\t$this->fonts[$css_ruleset->{'font-family'}]['source'] = 'Web Font Loader';\n\t\t\t\t\t\t$this->fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'\/'.$css_ruleset->{'font-style'}]['has_css'] = true;\n\t\t\t\t\t\t\/\/ only formats woff and woff2, so just use format as file extension slot\n\t\t\t\t\t\tif (!isset($this->fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'\/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}])) {\n\t\t\t\t\t\t\t$this->fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'\/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}] = [];\t\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$css_ruleset->url = $font_baseurl . $css_ruleset->url;\n\n\t\t\t\t\t\t$css_block = $this->create_css_from_ruleset($css_ruleset);\n\t\t\t\t\t\t$this->fonts[$css_ruleset->{'font-family'}][$css_ruleset->{'font-weight'}.'\/'.$css_ruleset->{'font-style'}][$css_ruleset->{'format'}][] = $css_block;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\/\/ collect font definitions\n\t\tforeach ($font_splfiles as $font_splfile) {\n\t\t\t$this->font_files_cnt ++;\n\t\t\t$font_ext = $font_splfile->getExtension();\n\t\t\t$font_details = $this->parse_font_name($font_splfile->getbasename('.'.$font_ext));\n\t\t\t$font_name = $font_details->name;\n\t\t\tif (in_array($font_name,array_values($json_font_families))) {\n\t\t\t\t\/\/ already found this font from Web Font Loader. Skip.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$font_weight = $font_details->weight;\n\t\t\t$font_style = $font_details->style;\n\t\t\t$font_path = str_replace($this->fonts_base->dir,'',$font_splfile->getPath());\n\t\t\t\/\/ encode every single path element since we might have spaces or special chars \n\t\t\t$font_path = implode('\/',array_map('rawurlencode',explode('\/',$font_path)));\n\t\t\t\/\/ create entry for this font name\n\t\t\tif (!array_key_exists($font_name,$this->fonts)) {$this->fonts[$font_name] = [];}\n\t\t\t\/\/ create entry for this font weight\/style \n\t\t\tif (!array_key_exists($font_weight.'\/'.$font_style,$this->fonts[$font_name])) {$this->fonts[$font_name][$font_weight.'\/'.$font_style] = [];}\n\t\t\t\/\/ store font details for this file\n\t\t\t$this->fonts[$font_name][$font_weight.'\/'.$font_style][strtolower($font_ext)] = $this->fonts_base->url . $font_path . '\/' . rawurlencode($font_splfile->getBasename());\n\t\t}\n\t\tksort($this->fonts, SORT_NATURAL | SORT_FLAG_CASE);\n\n\t\t\/\/ store font family names, prepare Gutenberg CSS classes\n\t\t$this->font_families = [];\n\t\t$this->gutenberg_font_classes = [];\n\t\tforeach (array_keys($this->fonts) as $font_name) {\n\t\t\t$this->font_families[] = $font_name;\n\t\t\t$font_slug = $this->get_font_slug($font_name);\n\t\t\t$this->gutenberg_font_classes[] = sprintf('.has-%s-font-family{font-family:\"%s\";}', $font_slug, $font_name);\n\t\t}\n\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\t$this->timing_collect_fonts = $et-$st;\n\t\t$this->memory_collect_fonts = $em-$sm;\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('    %s %d font files, %d font families.', __METHOD__, $this->font_files_cnt, count($this->fonts)));}\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('  %s runtime: %.5f sec., temp memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\tdo_action('qm\/stop', __METHOD__); \/\/ Query Monitor Profiling\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Parses weight and style from a font file name (not used for Web Font Loader packages).\n\t * @param string $name\t\t\t\tThe font file name\n\t * @return object\t\t\t\t\tObject with `name`, `weight`, `style`\n\t *\/\n\tprivate function parse_font_name(string $name): object {\n\t\t\/\/ already in cache?\n\t\tif (array_key_exists($name,$this->fonts_details_cache)) {return $this->fonts_details_cache[$name];}\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t$retval = (object)['name'=>$name, 'weight'=>400, 'style'=>'normal'];\n\t\t$weights = (object)[ \/\/ must match from more to less specific !!\n\t\t\t\/\/ more specific\n\t\t\t200 => '\/[ \\-]?(200|((extra|ultra)\\-?light))\/i',\n\t\t\t800 => '\/[ \\-]?(800|((extra|ultra)\\-?bold))\/i',\n\t\t\t600 => '\/[ \\-]?(600|([ds]emi(\\-?bold)?))\/i',\n\t\t\t\/\/ less specific\n\t\t\t100 => '\/[ \\-]?(100|thin)\/i',\n\t\t\t300 => '\/[ \\-]?(300|light)\/i',\n\t\t\t400 => '\/[ \\-]?(400|normal|regular|book)\/i',\n\t\t\t500 => '\/[ \\-]?(500|medium)\/i',\n\t\t\t700 => '\/[ \\-]?(700|bold)\/i',\n\t\t\t900 => '\/[ \\-]?(900|black|heavy)\/i',\n\t\t\t'var' => '\/[ \\-]?(VariableFont|\\[wght\\])\/i',\n\t\t];\n\t\t$count = 0;\n\t\t\/\/ detect & cut style\n\t\t$new_name = preg_replace('\/[ \\-]?(italic|oblique)\/i', '', $retval->name, -1, $count); \n\t\tif ($new_name && $count) {\n\t\t\t$retval->name = $new_name;\n\t\t\t$retval->style = 'italic';\n\t\t}\n\t\t\/\/ detect & cut weight\n\t\tforeach ($weights as $weight => $pattern) {\n\t\t\t$new_name = preg_replace($pattern, '', $retval->name, -1, $count);\n\t\t\tif ($new_name && $count) {\n\t\t\t\t$retval->name = $new_name;\n\t\t\t\t$retval->weight = $weight;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t\/\/ cut -webfont\n\t\t$retval->name = preg_replace('\/[ \\-]?webfont$\/i', '', $retval->name); \n\t\t\/\/ variable font: detect & cut specifica\n\t\tif ($retval->weight == 'var') {\n\t\t\t$retval->name = preg_replace('\/_(opsz,wght|opsz|wght)$\/i', '', $retval->name); \n\t\t}\n\t\t\/\/ store to cache\n\t\t$this->fonts_details_cache[$name] = $retval;\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>2) {error_log(sprintf('     %s runtime: %.5f sec., memory usage: %.2f KN', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\treturn $retval;\n\t}\n\t\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns CSS block from CSS properties stored in JSON from Web Font Loader\n\t * @param object $css_ruleset\t\tThe CSS ruleset from Web Font Loader\n\t * @return string\t\t\t\t\tThe CSS @font-face definition as string\n\t *\/\n\tprivate function create_css_from_ruleset(object $css_ruleset): string {\n\t\t$retval = '';\n\t\tif (isset($css_ruleset)) {\n\t\t\t$retval .= \"@font-face{\\n\";\n\t\t\tif (isset($css_ruleset->{'comment'})) {$retval .= sprintf(\"  \/* %s *\/\\n\",$css_ruleset->{'comment'});}\n\t\t\t$retval .= sprintf(\"  font-family:'%s';\\n\",$css_ruleset->{'font-family'});\n\t\t\t$retval .= sprintf(\"  font-style:%s;\\n\",$css_ruleset->{'font-style'});\n\t\t\t$retval .= sprintf(\"  font-weight:%s;\\n\",$css_ruleset->{'font-weight'});\n\t\t\t$retval .= sprintf(\"  src:url('%s') format('%s');\\n\",$css_ruleset->{'url'}, $css_ruleset->{'format'});\n\t\t\tif (isset($css_ruleset->{'unicode-range'})) {$retval .= sprintf(\"  unicode-range:%s;\\n\", $css_ruleset->{'unicode-range'});}\n\t\t\tif ($this->font_display) {\n\t\t\t\t$retval .= sprintf(\"  font-display:%s;\\n\",$this->font_display);\n\t\t\t}\t\t\t\n\t\t\t$retval .= '}';\n\t\t}\n\t\treturn $retval;\n\t}\n\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Creates CSS for custom fonts. Returned value depends on configuration `$css_output`:\n\t * - `file`: `<link rel=\"stylesheet\" ...>`\n\t * - `html`: `<style>...<\/style>`\n\t * @param bool $plaincss\t`true`to return plain CSS without `<link...>` or `<style><\/style>` tags. Default is `false`.\n\t * \t\t\t\t\t\t\tCurrently not used. \n\t * @return string\t\t\tThe CSS as string, either `<link...>` or `<style...>`, or plain CSS\n\t *\/\n\tfunction generate_font_css($plaincss = false): string {\n\t\tdo_action('qm\/start', __METHOD__); \/\/ Query Monitor Profiling\n\t\t\/\/ emit CSS for fonts in footer\n\t\t$style = '';\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\tforeach ($this->fonts as $font_name => $font_details) {\n\t\t\tksort($font_details);\n\t\t\tforeach ($font_details as $weight_style => $file_list) {\n\t\t\t\tif ($weight_style=='source') {continue;}\n\t\t\t\tlist ($font_weight,$font_style) = explode('\/',$weight_style);\n\n\t\t\t\tif (isset($file_list['has_css'])) {\n\t\t\t\t\t\/\/ V3: Google Font package CSS from Web Font Loader already has CSS\n\t\t\t\t\tforeach (array_reverse($this->prioritized_formats) as $font_ext) {\n\t\t\t\t\t\t\/\/ we only have woff and woff2\n\t\t\t\t\t\tif (!isset($file_list[$font_ext])) { continue; }\n\t\t\t\t\t\tforeach ($file_list[$font_ext] as $css) {\n\t\t\t\t\t\t\t$style .= trim($css).PHP_EOL;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\/\/ V2: Only have font info and file names. Build CSS\n\t\t\t\t\tif ($font_weight == 'var') {\n\t\t\t\t\t\t$font_weight_output = '1 1000';\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$font_weight_output = $font_weight;\n\t\t\t\t\t}\n\t\t\t\t\t$style .= \t'@font-face{'.PHP_EOL.\n\t\t\t\t\t\t\t\t'  font-family:\"'.$font_name.'\";'.PHP_EOL.\n\t\t\t\t\t\t\t\t'  font-weight:'.$font_weight_output.';'.PHP_EOL.\n\t\t\t\t\t\t\t\t'  font-style:'.$font_style.';'.PHP_EOL;\n\t\t\t\t\t\t\t\t\/\/ .eot needs special handling for IE9 Compat Mode\n\t\t\t\t\tif (array_key_exists('eot',$file_list)) {$style .= '  src:url(\"'.$file_list['eot'].'\");'.PHP_EOL;}\n\t\t\t\t\t$urls = [];\n\n\t\t\t\t\t\/\/ output font sources in prioritized order\n\t\t\t\t\tforeach ($this->prioritized_formats as $font_ext) {\n\t\t\t\t\t\tif (array_key_exists($font_ext,$file_list)) {\n\t\t\t\t\t\t\t$font_url = $file_list[$font_ext];\n\t\t\t\t\t\t\t$format = '';\n\t\t\t\t\t\t\tswitch ($font_ext) {\n\t\t\t\t\t\t\t\tcase 'eot': $format = 'embedded-opentype'; break;\n\t\t\t\t\t\t\t\tcase 'otf': $format = 'opentype'; break;\n\t\t\t\t\t\t\t\tcase 'ttf': $format = 'truetype'; break;\n\t\t\t\t\t\t\t\t\/\/ others have same format as extension (svg, woff, woff2)\n\t\t\t\t\t\t\t\tdefault:\t$format = strtolower($font_ext);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ($font_ext == 'eot') {\n\t\t\t\t\t\t\t\t\/\/ IE6-IE8\n\t\t\t\t\t\t\t\t$urls[] = 'url(\"'.$font_url.'?#iefix\") format(\"'.$format.'\")';\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$urls[] = 'url(\"'.$font_url.'\") format(\"'.$format.'\")';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t$style .= '  src:' . join(','.PHP_EOL.'      ',$urls) . ';'.PHP_EOL;\n\t\t\t\t\tif ($this->font_display) {\n\t\t\t\t\t\t$style .= sprintf('  font-display:%s;'.PHP_EOL, $this->font_display);\n\t\t\t\t\t}\n\t\t\t\t\t$style .= '}'.PHP_EOL;\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t}\n\n\t\t\/\/ if $gutenberg_font_family_select is enabled, add Gutenberg font classes\n\t\tif ($this->gutenberg_font_family_select && $this->gutenberg_font_classes) {\n\t\t\tforeach ($this->gutenberg_font_classes as $class) {\n\t\t\t\t$style .= $class.PHP_EOL;\n\t\t\t\t\/\/ prepare overrides for Oxygen default fonts\n\t\t\t\t$this->gutenberg_editor_font_styles[] = 'body .editor-styles-wrapper :is(h1,h2,h3,h4,h5,h6,.editor-post-title)'.$class.PHP_EOL;\n\t\t\t}\n\t\t}\n\n\t\t\/\/ minimize string if configured\n\t\tif ($this->css_minimize) {\n\t\t\t$style = preg_replace('\/\\r?\\n *(?![\\@\\.])\/', '', $style); \/\/ remove line breaks, except before @, .\n\t\t\t$style = preg_replace('\/\\t\/', '', $style); \/\/ remove tabs\n\t\t}\n\n\t\t$version = $this->get_script_details()->full;\n\t\t$retval = '';\n\t\t\/\/ Generate CSS file no matter how $css_output type is configured. \n\t\t\/\/ Block editor in FSE\/iframe mode doesn't handle inject @font-face, so we need to load the file in Gutenberg editor\n\t\t\/\/ write CSS to file\n\t\t$css_path = $this->fonts_base->dir.'\/'.self::SLUG.'.css';\n\t\t$css_code = '\/* Version: '.$version.' *\/'.PHP_EOL.$style;\n\t\t$css_hash_code = hash('CRC32', $css_code, false);\n\t\t$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;\n\t\tif ($css_hash_code !== $css_hash_file) {\n\t\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s Writing CSS file \"%s\"',__METHOD__,$css_path));}\n\t\t\t$status = file_put_contents($css_path, $css_code);\n\t\t\tif ($status === false) {error_log(sprintf('%s Error writing CSS file \"%s\"',__METHOD__,$css_path));}\n\t\t\t$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;\n\t\t}\n\t\tif ($this->css_output == 'file') {\n\t\t\t\/\/ always use full url, no matter how $css_font_urls is configured\n\t\t\t$css_url = str_replace($this->fonts_base->dir,$this->fonts_base->url_full ,$css_path);\n\t\t\t$retval = sprintf('<link id=\"%s\" href=\"%s?ver=%s\" rel=\"stylesheet\" type=\"text\/css\"\/>', self::SLUG, $css_url, $css_hash_file);\n\t\t}\n\t\tif ($this->css_output == 'html') {\n\t\t\t\/\/ option: write CSS to html\n\t\t\t$retval = sprintf('<style id=\"%s\">\/* Version: %s *\/%s<\/style>', self::SLUG, $version, PHP_EOL.$style);\n\t\t}\n\t\tif ($plaincss) {\n\t\t\t$retval = $style;\n\t\t}\n\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\t$this->timing_generate_css = $et-$st;\n\t\t$this->memory_generate_css = $em-$sm;\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('  %s runtime: %.5f sec., temp memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\tdo_action('qm\/stop', __METHOD__); \/\/ Query Monitor Profiling\n\t\treturn $retval;\n\t}\n\t\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns properly quoted font name(s) from font spec; detects multiple font names and quotes separately. Only quotes \n\t * font names when necessary (e.g. include spaces)\n\t * @param string $font_spec\t\tThe font name or a string with multiple font names (`Arial, Helvetica Neue`)\n\t * @return string \t\t\t\tThe quoted font name(s) quoted (`Arial, \"Helvetica Neue\"`)\n\t *\/\n\tprivate function quote_font_names(string $font_spec): string {\n\t\t$fonts = preg_split('\/,\\s*\/', $font_spec, -1, PREG_SPLIT_NO_EMPTY);\n\t\t$fonts = array_map(function($name){\n\t\t\treturn preg_match('\/[^A-Za-z0-9\\-]\/',$name) ? '\"'.$name.'\"' : $name;\n\t\t},$fonts);\n\t\t$retval = implode(', ',$fonts);\n\t\treturn $retval;\n\t}\n\n\t\/\/===================================================================================================================\n\t\/\/ ADMIN \/ SHORTCODE\n\t\/\/===================================================================================================================\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Executes actions only needed for admin:\n\t * - Loads CSS (file or inline) on pages using Gutenberg editor, and on the AM Custom Font test page\n\t * - Oxygen: Retrieves global standard fonts and  \n\t *\/\n\tpublic function admin_init(){\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\n\t\t\/\/ Load CSS for calls using Gutenberg Editor, or the MA Custom Fonts test page. \n\t\t\/\/ Requires ma-customfont.css (for font loading) and some Oxygen settings (for font assignment)\n\t\tglobal $pagenow;\n\n\t\t\/\/ MA Custom Fonts test page\n\t\tif ( ($pagenow === 'themes.php') && ($_REQUEST['page']??null === self::SLUG)) {\n\t\t\t\/\/ enqueue or embed ma-customfonts.css\n\t\t\tif ($this->css_output == 'file') {\n\t\t\t\t$css_path = $this->fonts_base->dir.'\/'.self::SLUG.'.css';\n\t\t\t\t$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;\n\t\t\t\twp_enqueue_style(self::SLUG, $this->fonts_base->url_full.'\/'.self::SLUG.'.css', [], $css_hash_file); \n\t\t\t} else {\n\t\t\t\tadd_action('admin_head', [$this, 'frontend_css'],5);\n\t\t\t}\n\t\t}\n\n\t\t\/\/ Gutenberg Editor, FSE site editor (@since 3.4.3)\n\t\tif ( ($pagenow === 'post-new.php') || ($pagenow === 'post.php' && ($_REQUEST['action']??'') === 'edit') || ($pagenow === 'site-editor.php')) {\n\n\t\t\t\/\/ Builder font settings in Gutenberg\n\t\t\t$builder_fonts_to_gutenberg = [];\n\n\n\t\t\tif (defined('CT_VERSION') && $this->builder_fonts_in_gutenberg && function_exists('ct_get_global_settings')) {\n\t\t\t\t\/\/ Create custom style for body, h1-h6 from Oxygen global settings\n\t\t\t\t\/\/ Google fonts are not loaded in Gutenberg. We only set styles for custom fonts and websave fonts.\n\t\t\t\t$ct_global_settings = call_user_func('ct_get_global_settings');\n\n\t\t\t\t\/\/ body font\n\t\t\t\t$body = [];\n\t\t\t\tif (in_array($ct_global_settings['fonts']['Text']??null, $this->font_families)) {\n\t\t\t\t\t$body['font-family'] = $this->quote_font_names($ct_global_settings['fonts']['Text']);\n\t\t\t\t}\n\t\t\t\t$body_size = $ct_global_settings['body_text']['font-size']??'';\n\t\t\t\t$body_size_unit = $ct_global_settings['body_text']['font-size-unit']??'';\n\t\t\t\tif ($body_size && $body_size_unit) {$body['font-size'] = $body_size . $body_size_unit;}\n\t\t\t\t$body['font-weight'] = $ct_global_settings['body_text']['font-weight']??null;\n\t\t\t\t$body['line-height'] = $ct_global_settings['body_text']['line-height']??null;\n\t\t\t\t$body_style = '';\n\t\t\t\t\/\/ TODO: check for simple numeric size\/weight\/line-height, since e.g. var() might not be provided\n\t\t\t\t\/\/foreach ($body as $property => $value) {if (!empty($value)) {$body_style .= $property.':'.$value.';';}}\n\t\t\t\t\/\/ for now, we only set font family\n\t\t\t\tif (!empty($body['font-family'])) {$body_style .= 'font-family:'.$body['font-family'].';';}\n\n\t\t\t\tif ($body_style) {\n\t\t\t\t\t$builder_fonts_to_gutenberg[] = sprintf('body .editor-styles-wrapper {%s}', $body_style);\n\t\t\t\t}\n\t\t\t\t\/\/ heading font\n\t\t\t\t$head = []; \/\/ define outside loop. we'll reuse the settings for each heading\n\t\t\t\tforeach (['H1','H2','H3','H4','H5','H6'] as $h) {\n\t\t\t\t\tif (in_array($ct_global_settings['fonts']['Display']??null, $this->font_families)) {\n\t\t\t\t\t\t$head['font-family'] = $this->quote_font_names($ct_global_settings['fonts']['Display']);\n\t\t\t\t\t}\n\t\t\t\t\t$head_size = $ct_global_settings['headings'][$h]['font-size']??'';\n\t\t\t\t\t$head_size_unit = $ct_global_settings['headings'][$h]['font-size-unit']??'';\n\t\t\t\t\tif ($head_size && $head_size_unit) {$head['font-size'] = $head_size . $head_size_unit;}\n\t\t\t\t\t$head['font-weight'] = $ct_global_settings['headings'][$h]['font-weight']??null;\n\t\t\t\t\t$head['line-height'] = $ct_global_settings['headings'][$h]['line-height']??null;\n\t\t\t\t\t$head_style = '';\n\t\t\t\t\t\/\/ TODO: check for simple numeric size\/weight\/line-height, since e.g. var() might not be provided\n\t\t\t\t\t\/\/foreach ($head as $property => $value) {if (!empty($value)) {$head_style .= $property.':'.$value.';';}}\n\t\t\t\t\t\/\/ for now, we only set font family\n\t\t\t\t\tif (!empty($head['font-family'])) {$head_style .= 'font-family:'.$head['font-family'].';';}\n\t\t\t\t\tif ($head_style) {\n\t\t\t\t\t\t$selector = $h == 'H1' ? ':is(h1,.editor-post-title)' : ':is('.strtolower($h).')';\n\t\t\t\t\t\t$builder_fonts_to_gutenberg[] = sprintf('body .editor-styles-wrapper %s {%s}', $selector, $head_style);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\/\/ Prepend to Gutenberg editor font styles\n\t\t\t\t$this->gutenberg_editor_font_styles = array_merge($builder_fonts_to_gutenberg, $this->gutenberg_editor_font_styles);\n\t\t\t}\n\n\t\t\tif (defined('BRICKS_VERSION') && defined('BRICKS_DB_THEME_STYLES') && $this->builder_fonts_in_gutenberg) {\n\t\t\t\t\/\/ Create custom style for body, h1-h6 from Bricks Theme styles\n\t\t\t\t$styles = get_option(constant('BRICKS_DB_THEME_STYLES'), []);\n\t\t\t\t$post_id = get_the_ID();\n\t\t\t\t$found_styles = [];\n\t\t\t\tforeach ( $styles as $style_id => $style ) {\n\t\t\t\t\t$conditions = $style['settings']['conditions']['conditions']??false;\n\t\t\t\t\tif (!is_array($conditions) ) {continue;}\n\t\t\t\t\tif (method_exists('Bricks\\Database','screen_conditions')) {\n\t\t\t\t\t\t$found_styles = call_user_func('Bricks\\Database::screen_conditions', $found_styles, $style_id, $conditions, $post_id, '');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t$style_id = null;\n\t\t\t\tif (!empty($found_styles)) {\n\t\t\t\t\tksort($found_styles, SORT_NUMERIC);\n\t\t\t\t\t$style_id = array_pop($found_styles);\n\t\t\t\t}\n\t\t\t\tif ($style_id) {\n\t\t\t\t\t$settings = $styles[$style_id]['settings']??[];\n\t\t\t\t}\n\n\t\t\t\t\/\/ body font\n\t\t\t\t$body = [];\n\t\t\t\tif (in_array($settings['typography']['typographyBody']['font-family']??null, $this->font_families)) {\n\t\t\t\t\t$body['font-family'] = $this->quote_font_names($settings['typography']['typographyBody']['font-family']??null);\n\t\t\t\t}\n\t\t\t\t$body['font-size'] = $settings['typography']['typographyBody']['font-size']??null;\n\t\t\t\t$body['font-weight'] = $settings['typography']['typographyBody']['font-weight']??null;\n\t\t\t\t$body['line-height'] = $settings['typography']['typographyBody']['line-height']??null;\n\t\t\t\t$body_style = '';\n\t\t\t\t\/\/ TODO: check for simple numeric size\/weight\/line-height, since e.g. var() might not be provided\n\t\t\t\t\/\/foreach ($body as $property => $value) {if (!empty($value)) {$body_style .= $property.':'.$value.';';}}\n\t\t\t\tif (!empty($body['font-family'])) {$body_style .= 'font-family:'.$body['font-family'].';';}\n\t\t\t\tif ($body_style) {\n\t\t\t\t\t$builder_fonts_to_gutenberg[] = sprintf('body .editor-styles-wrapper {%s}', $body_style);\n\t\t\t\t}\n\n\t\t\t\t\/\/ heading font\n\t\t\t\t$head = []; \/\/ define outside loop. we'll reuse the settings for each heading\n\t\t\t\t$head_defaults = [\n\t\t\t\t\t'H1'\t=> ['font-size'=>'2.4em', 'line-height'=>'1.4'],\n\t\t\t\t\t'H2'\t=> ['font-size'=>'2.1em', 'line-height'=>'1.4'],\n\t\t\t\t\t'H3'\t=> ['font-size'=>'1.8em', 'line-height'=>'1.4'],\n\t\t\t\t\t'H4'\t=> ['font-size'=>'1.6em', 'line-height'=>'1.4'],\n\t\t\t\t\t'H5'\t=> ['font-size'=>'1.3em', 'line-height'=>'1.4'],\n\t\t\t\t\t'H6'\t=> ['font-size'=>'1.1em', 'line-height'=>'1.4'],\n\t\t\t\t];\n\t\t\t\tforeach (['s','H1','H2','H3','H4','H5','H6'] as $h) {\n\t\t\t\t\t\/\/ s = global settings\n\t\t\t\t\tif (in_array($settings['typography']['typographyHeading'.$h]['font-family']??null, $this->font_families)) {\n\t\t\t\t\t\t$head['font-family'] = $this->quote_font_names($settings['typography']['typographyHeading'.$h]['font-family']);\n\t\t\t\t\t}\n\t\t\t\t\t$head['font-size'] = $settings['typography']['typographyHeading'.$h]['font-size']??$head_defaults[$h]['font-size']??null;\n\t\t\t\t\t$head['font-weight'] = $settings['typography']['typographyHeading'.$h]['font-weight']??null;\n\t\t\t\t\t$head['line-height'] = $settings['typography']['typographyHeading'.$h]['line-height']??$head_defaults[$h]['line-height']??null;\n\t\t\t\t\t$head_style = '';\n\t\t\t\t\t\/\/ TODO: check for simple numeric size\/weight\/line-height, since e.g. var() might not be provided\n\t\t\t\t\t\/\/foreach ($head as $property => $value) {if (!empty($value)) {$head_style .= $property.':'.$value.';';}}\n\t\t\t\t\t\/\/ for now, we only set font family\n\t\t\t\t\tif (!empty($head['font-family'])) {$head_style .= 'font-family:'.$head['font-family'].';';}\n\t\t\t\t\tif ($head_style && ($h != 's')) {\n\t\t\t\t\t\t$selector = $h == 'H1' ? ':is(h1,.editor-post-title)' : ':is('.strtolower($h).')';\n\t\t\t\t\t\t$builder_fonts_to_gutenberg[] = sprintf('body .editor-styles-wrapper %s {%s}', $selector, $head_style);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\/\/ Prepend to Gutenberg editor font styles\n\t\t\t\t$this->gutenberg_editor_font_styles = array_merge($builder_fonts_to_gutenberg, $this->gutenberg_editor_font_styles);\n\t\t\t}\n\n\t\t\t\/\/ Custom Fonts in Gutenberg\n\t\t\t\/\/ Block editor in FSE\/iframe mode doesn't handle @font-face rules as inline. \n\t\t\t\/\/ Load external CSS no matter how $css_output is configured\n\t\t\tif (count($this->gutenberg_editor_font_styles)) {\n\t\t\t\tadd_filter('block_editor_settings_all', function($editor_settings, $editor_context){\n\t\t\t\t\t\/\/ add font-size 16\n\t\t\t\t\t$styles = implode(PHP_EOL, $this->gutenberg_editor_font_styles);\n\t\t\t\t\t$styles = preg_replace('\/^body \\.editor\\-styles\\-wrapper\/m','body', $styles);\n\t\t\t\t\t$editor_settings['styles'][] = ['css' => $styles];\n\t\t\t\t\treturn $editor_settings;\n\t\t\t\t}, 10,2 );\n\t\t\t\tadd_action('enqueue_block_assets', function(){\n\t\t\t\t\t$css_path = $this->fonts_base->dir.'\/'.self::SLUG.'.css';\n\t\t\t\t\t$css_hash_file = file_exists($css_path) ? hash_file('CRC32', $css_path, false) : 0;\n\t\t\t\t\t\/\/ enqueue with relative path instead of our URL since block assets in FSE iframe can't be used scheme-less\n\t\t\t\t\t$css_rel_path = str_replace(ABSPATH,'\/',$css_path);\n\t\t\t\t\twp_enqueue_style(self::SLUG, $css_rel_path, [], $css_hash_file);\n\t\t\t\t});\n\t\t\t}\n\n\t\t}\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s runtime: %.5f sec., memory usage: %.2f KB',__METHOD__,$et-$st, ($em-$sm)\/1024));}\n\t\t$this->timing_total_runtime += $et-$st;\n\t\t$this->memory_total_usage += $em-$sm;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Emits frontend CSS. Can be either a link or inline depending on configuration $css_output.\n\t *\/\n\tpublic function frontend_css(){\n\t\tdo_action('qm\/start', __METHOD__); \/\/ Query Monitor Profiling\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t\/\/ common custom font definition, optionally contains Gutenberg .has-xyz-font-family classes\n\t\techo $this->font_css;\n\n\t\t\/\/ Oxygen Builder: Add custom styles for font lists\n\t\tif (defined('CT_VERSION') && defined('SHOW_CT_BUILDER') && (!defined('OXYGEN_IFRAME'))) {\n\t\t\t$oxygen_builder_styles = [];\n\t\t\t\/\/ Show custom fonts in light blue.\n\t\t\t$selector = '.oxygen-select-box-option.ng-binding.ng-scope[ng-repeat*=\"elegantCustomFonts\"]'; \n\t\t\t$oxygen_builder_styles[] = $selector.'{color:lightblue;justify-content:flex-start;}';\n\t\t\t$oxygen_builder_styles[] = $selector.'::before {content:\"Custom\";margin-right:5px;padding:2px 3px;background-color:lightblue;border-radius:2px;color:black;font-size:75%}';\n\t\t\t\/\/ Optiopnal: Hide \"Top 20\" Google fonts if Google fonts are disabled\n\t\t\tif (get_option('oxygen_vsb_disable_google_fonts')??null == 'true') {\n\t\t\t\t$selector = '.oxygen-select-box-option.ng-binding[ng-repeat^=\"font in iframeScope.googleFontsList\"]';\n\t\t\t\t$oxygen_builder_styles[] = $selector.'{display:none;}';\n\t\t\t} \n\t\t\techo sprintf('<style id=\"%s-oxygen\">%s<\/style>', self::SLUG, implode(PHP_EOL, $oxygen_builder_styles));\n\t\t}\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s runtime %.5f sec., temp memory usage: %.2f KB',__METHOD__,$et-$st, ($em-$sm)\/1024));}\n\t\t$this->timing_total_runtime += $et-$st;\n\t\t$this->memory_total_usage += $em-$sm;\n\t\tdo_action('qm\/stop', __METHOD__); \/\/ Query Monitor Profiling\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/** @since 3.4.0 *\/\n\tpublic function debug_info(){\n\t\t\/** @since 1.2.0 debugging info *\/ \n\t\techo sprintf('<span id=\"%s-info\" data-nosnippet style=\"display:none\">%s %s %s<\/span>', self::SLUG, $this->get_script_details()->type, self::SLUG, self::VERSION); \n\t}\n\t\n\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Registers the Admin menu Appearance > MA Custom Fonts.\n\t *\/\n\tpublic function admin_init_menu() {\n\t\tadd_submenu_page(\t'themes.php', \t\t\t\t\t\t\t\t\t\/\/ parent slug of \"Appearance\"\n\t\t\t\t\t\t\t_x(self::TITLE,'page title', 'ma-customfonts'), \/\/ page title\n\t\t\t\t\t\t\t_x(self::TITLE,'menu title', 'ma-customfonts'), \/\/ menu title\n\t\t\t\t\t\t\t'manage_options',\t\t\t\t\t\t\t\t\/\/ capabilitiy\n\t\t\t\t\t\t\tself::SLUG,\t\t\t\t\t\t\t\t\t\t\/\/ menu slug\n\t\t\t\t\t\t\t[$this, 'admin_customfonts']\t\t\t\t\t\/\/ function\n\t\t);\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Outpuths the admin page Appearance > MA Custom Fonts to display samples of all detected fonts\n\t *\/\n\tpublic function admin_customfonts() {\n\t\t$output =\t'<div class=\"wrap\">'.\n\t\t\t\t\t\t'<h1>' . esc_html(get_admin_page_title()) . '<\/h1>'.\n\t\t\t\t\t\t$this->get_font_samples('admin').\n\t\t\t\t\t'<\/div>';\n\t\techo $output;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns parsed font file url from CSS block\n\t * @param string|array $css\t\t\tThe CSS to pasre (typically a @font-face ruleset)\n\t * @return array\t\t\t\t\tThe URLs parsed from CSS\n\t *\/\n\tprivate function get_font_file_info_from_css($css) {\n\t\t$retval = [];\n\t\tif (!is_array($css)) {$css = [$css];}\n\t\tforeach($css as $css_block) {\n\t\t\tif (preg_match('\/url\\(\\'(.*?)\\'\\)\/',$css_block,$matches)) {\n\t\t\t\t$retval[] = $matches[1];\n\t\t\t}\n\t\t}\n\t\t$retval = array_unique($retval); \n\t\treturn $retval;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns font file urls for var weight\n\t * @param array $font_deatils\t\tThe font details (array with coollected font details)\n\t * @return array\t\t\t\t\tArray with relative paths to the variable weight files, or empty array if none\n\t *\/\n\tprivate function get_font_var_weight_urls(?array $font_details): array {\n\t\t$retval = [];\n\t\tif (isset($font_details) && (is_array($font_details))) {\n\t\t\t\/\/ check if we have a 'var' weight array key\n\t\t\tforeach ($font_details as $weight_style => $details) {\n\t\t\t\t\/\/ check key (for custom fonts)\n\t\t\t\tif (strpos($weight_style,'var') === 0) {\n\t\t\t\t\tforeach ($this->var_weight_formats as $format) { \/\/ support var weight for specific formats\n\t\t\t\t\t\tif (isset($details[$format])) {\n\t\t\t\t\t\t\t$retval[] = urldecode($details[$format]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tgoto DONE;\n\t\t\t\t}\n\t\t\t\t\/\/ check CSS file names of woff2 files (for WebFontLoader fonts)\n\t\t\t\tif (isset($details['woff2']) && is_array($details['woff2'])) {\n\t\t\t\t\tforeach ($details['woff2'] as $css_rule) {\n\t\t\t\t\t\tif (preg_match('\/src: url\\(\\'(.*?)\\'\\)\/',$css_rule,$matches)) {\n\t\t\t\t\t\t\t$src = $matches[1];\n\t\t\t\t\t\t\tif (preg_match('\/[ \\-]?(VariableFont|\\[wght\\])\/i',$src)) {\n\t\t\t\t\t\t\t\t$retval[] = $src;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tDONE:\n\t\t\/\/ remove duplicates\n\t\t$retval = array_unique($retval);\n\t\t\/\/ remove base url\n\t\tforeach ($retval as &$url) {\n\t\t\t$url = str_replace($this->fonts_base->url.'\/','',$url);\n\t\t}\n\t\treturn $retval;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns font url (string or array) shortened and urldecoded\n\t * @param ?string|array $urls\t\tThe full URL(s)\n\t * @return array\t\t\t\t\tThe relative URLs rawdecoded\n\t *\/\n\tprivate function get_font_short_display_url($urls) {\n\t\t$retval = [];\n\t\tif ($urls) {\n\t\t\tif (!is_array($urls)) {$urls = [$urls];}\n\t\t\tforeach ($urls as $url) {\n\t\t\t\t\/\/ cut leading path\/url from file info\n\t\t\t\t$url = str_replace($this->fonts_base->url.'\/','',$url);\n\t\t\t\t\/\/ decode html entities (e.g. %20) in file path\n\t\t\t\t$url = implode('\/',array_map('rawurldecode',explode('\/',$url)));\n\t\t\t\t$retval[] = $url;\n\t\t\t}\n\t\t}\n\t\treturn implode(', ',$retval);\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns HTML to display samples of registered fonts. \n\t * @param string $mode Format of returned page:\n\t * - `admin`: \t\tformatting to be displayed on WP Admin > Appearance\n\t * - `shortcode`:\tformatting to be displayed as shortcode output\n\t * @return string\t\tThe HTML as string\n\t *\/\n\tprivate function get_font_samples($mode = null): string {\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t$script_details = $this->get_script_details()->full;\n\t\t$sample_text = $this->sample_text;\n\t\t$output_style = <<<'END_OF_STYLE'\n\t\t<style>\n\t\t.ma-customfonts-wrap {font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Ubuntu,Cantarell,Helvetica,sans-serif;font-size:13px;margin:20px 0;}\n\t\t.ma-customfonts-wrap h3 {margin-top:20px;padding-bottom:0;}\n\n\t\t.ma-customfonts-header {border:1px solid darkgray;border-radius:10px;padding:10px;padding-top:0;}\n\t\t.ma-customfonts-header > div {display:flex;flex-direction:row;margin-top:.5em;}\n\t\t.ma-customfonts-label {flex-shrink:0;display:inline-block;width:110px;}\n\n\t\t#ma-customfonts-input-font-size {width:60px;text-align:center;min-height:1.5em;line-height:1em;padding:0;}\n\t\t#ma-customfonts-input-sample-text {width:100%;max-width:400px;text-align:left;}\n\n\t\t.ma-customfonts-legend {margin-top:3em;border:1px solid darkgray;border-radius:10px;padding:10px;padding-top:0;}\n\t\t.ma-customfonts-legend > div {display:flex;flex-direction:row;margin-top:.5em;}\n\t\t.ma-customfonts-legend > div > span:first-child {width:15ch;flex-shrink:0;}\n\n\t\t.ma-customfonts-font-additional-info {margin-left:1em;margin-bottom:1em;font-size:13px;font-weight:normal;}\n\n\t\t.ma-customfonts-font-row {display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:0;line-height:20px;border-bottom:1px solid #e0e0e0;margin:0 1em;}\n\t\t.ma-customfonts-font-row:hover {background-color:lightgray;}\n\t\t.ma-customfonts-font-info {font-size:10px;line-height:1em;width:100px;}\n\t\t.ma-customfonts-font-sample {font-size:16px;line-height:1.2em;flex-grow:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}\n\t\t.ma-customfonts-format-info {font-size:10px;cursor:help;margin-left:1em;}\n\t\t.ma-customfonts-format-info.simulated {color:gray;font-style:italic;cursor:alias;} \n\t\t.ma-customfonts-format-info :is(.eot,.ttf,.svg) {color:red;}\n\t\t.ma-customfonts-format-info :is(.ttf,.otf) {color:chocolate;}\n\t\t.ma-customfonts-format-info :is(.woff) {color:orange;}\n\t\t.ma-customfonts-format-info :is(.woff2) {color:green;}\n\n\t\t.ma-customfonts-simulated {display:none;}\n\t\t@media (max-width:700px) {\n\t\t\t.ma-customfonts-font-row {flex-wrap:wrap;}\n\t\t\t.ma-customfonts-font-info {order:1;}\n\t\t\t.ma-customfonts-font-sample {order:3; width:100%;}\n\t\t\t.ma-customfonts-format-info {order:2;}\n\t\t}\n\t\t<\/style>\nEND_OF_STYLE;\n\t\t$var = (object)[\n\t\t\t'container_style'\t=> $mode=='shortcode' ? 'style=\"border:1px dashed darkgray;padding:10px;\"' : '',\n\t\t\t'title'\t\t\t\t=> $mode=='shortcode' ? '<h2>'.self::TITLE.'<\/h2>' : '',\n\t\t\t'cnt_families'\t\t=> count($this->fonts),\n\t\t\t'cnt_files'\t\t\t=> $this->font_files_cnt,\n\t\t];\n\t\t$output_header = <<<END_OF_HEADER\n\t\t<div class=\"ma-customfonts-wrap\" {$var->container_style}>\n\t\t\t{$var->title}\n\t\t\t<div class=\"ma-customfonts-header\">\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Version:<\/span>\n\t\t\t\t\t<span>{$script_details}<span>\n\t\t\t\t<\/div>\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Font Families:<\/span>\n\t\t\t\t\t<span>{$var->cnt_families}<span>\n\t\t\t\t<\/div>\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Font Files:<\/span>\n\t\t\t\t\t<span>{$var->cnt_files}<\/span>\n\t\t\t\t<\/div>\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Sample Font Size:<\/span>\n\t\t\t\t\t<span><input id=\"ma-customfonts-input-font-size\" type=\"number\" value=\"16\" onchange=\"ma_customfonts_change_font_size();\"> px<\/span>\n\t\t\t\t<\/div>\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Sample Text:<\/span>\n\t\t\t\t\t<input id=\"ma-customfonts-input-sample-text\" value=\"{$sample_text}\" onkeyup=\"ma_customfonts_change_sample_text();\">\n\t\t\t\t<\/div>\n\t\t\t\t<div>\n\t\t\t\t\t<span class=\"ma-customfonts-label\">Simulated:<\/span>\n\t\t\t\t\t<span><input id=\"ma-customfonts-input-simulated\" type=\"checkbox\" value=\"simulated\" onchange=\"ma_customfonts_toggle_simulated();\"> Show font weights\/styles without files as browser would simulate.<\/span>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\nEND_OF_HEADER;\n\t\t$output_legend = <<<END_OF_LEGEND\n\t\t<fieldset class=\"ma-customfonts-legend\">\n\t\t\t<legend><strong>Font File Formats<\/strong><\/legend>\n\t\t\t<div><span style=\"color:green\">WOFF2<\/span><span>Modern formats are perfect for web.<\/span><\/div>\n\t\t\t<div><span style=\"color:orange\">WOFF<\/span><span>Older formats can be used for web but have larger file sizes.<\/span><\/div>\n\t\t\t<div><span style=\"color:chocolate\">OTF, TTF<\/span><span>Formats meant for desktop use, can be used for web but have larger to huge file sizes.<\/span><\/div>\n\t\t\t<div><span style=\"color:red\">EOT, SVG<\/span><span>Some formats are only supported on specific browsers like EOT on IE, SVG on Safari. Deprecated!<\/span><\/div>\n\t\t<\/fieldset>\nEND_OF_LEGEND;\n\t\t\/\/ controls\n\t\t$output_script = <<<'END_OF_SCRIPT'\n\t\t<script>\n\t\tfunction changeCss($className, $classValue) {\n\t\t\t\/\/ we need invisible container to store additional css definitions\n\t\t\tlet $cssMainContainer = document.querySelector('#ma-customfonts-css-modifier-container');\n\t\t\tif ($cssMainContainer === null) {\n\t\t\t\t$cssMainContainer = document.createElement('div');\n\t\t\t\t$cssMainContainer.id = 'ma-customfonts-css-modifier-container';\n\t\t\t\t$cssMainContainer.style.display = 'none';\n\t\t\t\tdocument.body.appendChild($cssMainContainer);\n\t\t\t}\n\t\t\t\/\/ we need one div for each class\n\t\t\tlet $classContainer = $cssMainContainer.querySelector('div[data-class=\"' + $className + '\"]');\n\t\t\tif ($classContainer === null) {\n\t\t\t\t$classContainer = document.createElement('div');\n\t\t\t\t$classContainer.dataset.class = $className;\n\t\t\t\t$cssMainContainer.appendChild($classContainer);\n\t\t\t}\n\t\t\t\/\/ set class style\n\t\t\t$classContainer.innerHTML = '<style type=\"text\/css\">.'+$className+' {'+$classValue+'}<\/style>';\n\t\t}\n\t\tfunction ma_customfonts_change_font_size() {\n\t\t\tlet $val = document.querySelector('#ma-customfonts-input-font-size').value;\n\t\t\tchangeCss('ma-customfonts-font-sample','font-size: '+$val+'px;');\n\t\t}\n\t\tfunction ma_customfonts_change_sample_text() {\n\t\t\tlet $val = document.querySelector('#ma-customfonts-input-sample-text').value;\n\t\t\tdocument.querySelectorAll('.ma-customfonts-font-sample').forEach( ($elm) => {$elm.textContent=$val;} );\n\t\t}\n\t\tfunction ma_customfonts_toggle_simulated() {\n\t\t\tlet $simulated = document.querySelector('#ma-customfonts-input-simulated').checked;\n\t\t\tdocument.querySelectorAll('.ma-customfonts-simulated').forEach( ($elm) => {$elm.style.display = $simulated?'flex':'none';} );\n\t\t}\n\t\t<\/script>\nEND_OF_SCRIPT;\n\t\t\n\n\t\t\/\/ prepare tags for every weight\/style combination\n\t\t$weights = [100,200,300,400,500,600,700,800,900];\n\t\t$styles = ['normal','italic'];\n\t\t$weights_styles = [];\n\t\tforeach ($weights as $weight) { foreach ($styles as $style) { $weights_styles[] = $weight.'\/'.$style; } }\n\t\t\/\/ prepare data structure to display fonts in each weight\/style combination\n\t\t$samples = [];\n\t\tforeach (array_keys($this->fonts) as $font_name) {\n\t\t\t$samples[$font_name] = [\n\t\t\t\t'has_var_weight' => false,\n\t\t\t\t'weights_styles' => [],\n\t\t\t];\n\t\t\tforeach ($weights_styles as $weight_style) {\n\t\t\t\t$samples[$font_name]['weights_styles'][$weight_style] = [];\n\t\t\t}\n\t\t}\n\t\t\/\/ collect available font files\n\t\tforeach ($this->fonts as $font_name => $font_details) {\n\t\t\tksort($font_details);\n\n\t\t\t\/\/ check if we have a 'var' weight\n\t\t\t$font_var_weight_urls = $this->get_font_var_weight_urls($font_details);\n\t\t\tif ($font_var_weight_urls) {$samples[$font_name]['has_var_weight'] = true;}\n\t\t\t$samples[$font_name]['source'] = $font_details['source'] ?? null;\n\n\t\t\t\/\/ loop font details and fill available samples and formats\n\t\t\tforeach ($font_details as $weight_style => $formats) {\n\t\t\t\tif ($weight_style=='source') {continue;}\n\t\t\t\tlist ($weight,$style) = explode('\/',$weight_style);\n\t\t\t\t\n\t\t\t\tif ($weight == 'var') {\n\t\t\t\t\t\/\/  var weight detected from file name; we don't know the supported weights; fill all weights for current style\n\t\t\t\t\tforeach ($this->var_weight_formats as $format) { \/\/ support var weight for specific formats\n\t\t\t\t\t\tif (isset($formats[$format])) {\n\t\t\t\t\t\t\t$url = $formats[$format];\n\t\t\t\t\t\t\t$url = $this->get_font_short_display_url($url);\n\t\t\t\t\t\t\tforeach ($weights as $var_weight) {\n\t\t\t\t\t\t\t\t$samples[$font_name]['weights_styles'][$var_weight.'\/'.$style][$format] = $url;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} \n\t\t\t\t\/\/ fill non var weight formats \n\t\t\t\tforeach ($formats as $format => $url) {\n\t\t\t\t\tif (!in_array($format,$this->prioritized_formats)) {continue;} \/\/ skip 'has_css'\n\t\t\t\t\tif ($weight != 'var') {\n\t\t\t\t\t\tif (is_array($url)) { \/\/ seems to be an array of CSS rules\n\t\t\t\t\t\t\t$url = $this->get_font_file_info_from_css($url);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$url = $this->get_font_short_display_url($url);\n\t\t\t\t\t\t$samples[$font_name]['weights_styles'][$weight.'\/'.$style][$format] = $url;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t$output_sample = '';\n\t\tforeach ($samples as $font_name => $font_details) {\n\t\t\t\n\t\t\t\/\/ output the font sample block\n\t\t\t$output_sample .= sprintf('<h3>%1$s<\/h3>',$font_name);\n\t\t\t\/\/ additional font info (variable fonts, source Web Font Loader, WOFF support)\n\t\t\t$font_additional_infos = [];\n\t\t\tif ($font_details['has_var_weight']) {$font_additional_infos[] = 'Variable Weight Font.';}\n\t\t\tif ($font_details['source'] == 'Web Font Loader') {\n\t\t\t\t$font_additional_infos[] = 'Downloaded from <a href=\"https:\/\/webfontloader.altmann.de\/\" target=\"_blank\">Web Font Loader<\/a>.';\n\t\t\t\tif (!$this->wfl_support_woff) {$font_additional_infos[] = 'WOFF files are ignored according to configuration setting.';}\n\t\t\t}\n\t\t\t$output_sample .= count($font_additional_infos) ? '<div class=\"ma-customfonts-font-additional-info\">'.implode(' ',$font_additional_infos).'<\/div>' : '';\n\n\t\t\tforeach ($font_details['weights_styles'] as $weight_style => $formats) {\n\t\t\t\tif ($weight_style=='source') {continue;}\n\t\t\t\tlist ($weight,$style) = explode('\/',$weight_style);\n\t\t\t\t\/\/ build font file info output\n\t\t\t\t$font_file_list = [];\n\t\t\t\tforeach ($formats as $format => $files) {\n\t\t\t\t\tif (is_array($files)) {$files = implode(\"\\n\",$files);}\n\t\t\t\t\t$font_file_list[$format] = sprintf('<span class=\"%3$s\" title=\"%2$s\">%1$s<\/span>', strtoupper($format), $files, $format);\n\t\t\t\t}\n\t\t\t\t$font_file_info = '<span class=\"ma-customfonts-format-info\">(' . implode(', ',array_values($font_file_list)) . ')<\/span>';\n\t\t\t\t$output_sample .= sprintf(\t'<div class=\"ma-customfonts-font-row '.($font_file_list?'':'ma-customfonts-simulated').'\">'.\n\t\t\t\t\t\t\t\t\t\t\t\t'<span class=\"ma-customfonts-font-info\">%2$s %3$s<\/span>'.\n\t\t\t\t\t\t\t\t\t\t\t\t'<span class=\"ma-customfonts-font-sample\" style=\"font-family:\\'%1$s\\';font-weight:%2$d;font-style:%3$s\">%4$s<\/span>'.\n\t\t\t\t\t\t\t\t\t\t\t\t'%5$s'.\n\t\t\t\t\t\t\t\t\t\t\t'<\/div>',$font_name, $weight, $style, $sample_text, $font_file_list?$font_file_info:'<span class=\"ma-customfonts-format-info simulated\">(simulated)<\/span>');\n\t\t\t}\n\t\t}\n\t\t\/\/ output timing\n\t\t$runtime = (object)[\n\t\t\t'collect_fonts'\t=> sprintf('runtime: %.4f sec.',$this->timing_collect_fonts),\n\t\t\t'generate_css'\t=> sprintf('runtime: %.4f sec.',$this->timing_generate_css),\n\t\t\t'total'\t\t\t=> sprintf('runtime: %.4f sec.',$this->timing_total_runtime),\n\t\t\t'test_page'\t\t=> sprintf('runtime: %.4f sec.',microtime(true)-$st),\n\t\t];\n\t\t$memory = (object)[\n\t\t\t'collect_fonts'\t=> sprintf('temp memory usage: %.2f KB',$this->memory_collect_fonts\/1024),\n\t\t\t'generate_css'\t=> sprintf('temp memory usage: %.2f KB',$this->memory_generate_css\/1024),\n\t\t\t'total'\t\t\t=> sprintf('net memory usage: %.2f KB',$this->memory_total_usage\/1024),\n\t\t\t'test_page'\t\t=> sprintf('temp memory usage: %.2f KB',(memory_get_usage()-$sm)\/1024),\n\t\t];\n\t\t$output_timing = <<<END_OF_TIMING\n\t\t<fieldset class=\"ma-customfonts-legend\">\n\t\t\t<legend><strong>Timing &amp; Memory<\/strong><\/legend>\n\t\t\t<div><span>Collect fonts:<\/span><span>{$runtime->collect_fonts}, {$memory->collect_fonts}<\/span><\/div>\n\t\t\t<div><span>Generate CSS:<\/span><span>{$runtime->generate_css}, {$memory->generate_css}<\/span><\/div>\n\t\t\t<div><span>Total:<\/span><span>{$runtime->total}, {$memory->total}<\/span><\/div>\n\t\t\t<div><span>Test page:<\/span><span>{$runtime->test_page}, {$memory->test_page}<\/span><\/div>\n\t\t<\/fieldset>\nEND_OF_TIMING;\n\n\n\n\t\t$output =  $output_style . $output_header . $output_script . $output_sample . $output_legend . $output_timing.'<\/div>';\n\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s runtime: %.5f sec., temp memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\treturn $output;\n\n\t}\n\t\/\/===================================================================================================================\n\t\/\/ GUTENBERG\n\t\/\/===================================================================================================================\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Injects font families into theme.json\n\t * @param WP_Theme_JSON_Data $theme_json\t\tThe theme.json object\n\t * @return WP_Theme_JSON_Data\t\t\t\t\tThe modified object\n\t *\/\n\tpublic function theme_json_inject_fonts(WP_Theme_JSON_Data $theme_json): WP_Theme_JSON_Data {\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t$fontFamilies = [];\n\t\tforeach ($this->font_families as $name) {\n\t\t\t$slug = $this->get_font_slug($name);\n\t\t\t$fontFamilies[] = [\n\t\t\t\t'fontFamily'\t=> '\"'.$name.'\"',\n\t\t\t\t'slug'\t\t\t=> $slug,\n\t\t\t\t'name'\t\t\t=> $name,\n\t\t\t];\n\t\t}\n\t\t$new_data = [\n\t\t\t'version' \t=> 2,\n\t\t\t'settings' \t=> [\n\t\t\t\t'typography' => [\n\t\t\t\t\t'fontFamilies' => $fontFamilies,\n\t\t\t\t]\n\t\t\t]\n\t\t];\n\t\t$retval = $theme_json->update_with($new_data);\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s runtime: %.5f sec., memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\t$this->timing_total_runtime += $et-$st;\t\n\t\t$this->memory_total_usage += $em-$sm;\n\t\treturn $retval;\n\t}\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Modifies Gutenberg blocks config via JS filter.\n\t *\/\n\tpublic function blocks_config() {\n\t\t$st = microtime(true);\n\t\t$sm = memory_get_usage();\n\t\t?>\n\t\t<script>\n\t\twindow.wp && wp.hooks && wp.hooks.addFilter( 'blocks.registerBlockType', 'ma\/customfonts', ( settings, name ) => {\n\t\t\tif (settings.supports && settings.supports.typography) {\n\t\t\t\tsettings.supports.typography.__experimentalDefaultControls = {\n\t\t\t\t\t...settings.supports.typography.__experimentalDefaultControls, \n\t\t\t\t\t...{fontFamily:true} \n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn settings;\n\t\t} )\n\t\t<\/script>\n\t\t<?php\n\t\t$et = microtime(true);\n\t\t$em = memory_get_usage();\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s runtime: %.5f sec., memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\t$this->timing_total_runtime += $et-$st;\t\n\t\t$this->memory_total_usage += $em-$sm;\n\t}\n\t\/\/===================================================================================================================\n\t\/\/ UTILS\n\t\/\/===================================================================================================================\n\t\/\/-------------------------------------------------------------------------------------------------------------------\n\t\/**\n\t * Returns the script details as type, version, combined.\n\t * @return object\tThe script  type and version:\n\t * - `type`: The type as 'Plugin' or 'Code Snippet'.\n\t * - `version`: The version.\n\t * - `combined`: The combination of type and version.\n\t * - `full`: The combination of type, title and version\n\t *\/\n\tpublic function get_script_details() {\n\t\t$type = basename(__FILE__) == 'ma-customfonts.php' ? 'Plugin' : 'Code Snippet';\n\t\t$retval =(object)[\n\t\t\t'type'\t\t=> $type,\n\t\t\t'version'\t=> self::VERSION,\n\t\t\t'combined'\t=> sprintf('%s %s', $type, self::VERSION),\n\t\t\t'full'\t\t=> sprintf('%s \"%s\" %s', $type, self::TITLE, self::VERSION),\n\t\t];\n\t\treturn $retval;;\n\t}\n\n} \/\/ end of class MA_CustomFonts\n\n\/\/ Initialization\nnew MA_CustomFonts();\n\n\/\/-------------------------------------------------------------------------------------------------------------------\n\/\/ create a primitive ECF_Plugin class if plugin \"Elegant Custom Fonts\" is not installed\nif (!class_exists('ECF_Plugin')) {\n\tclass ECF_Plugin {\n\t\tstatic function get_font_families() {\n\t\t\t$st = microtime(true);\n\t\t\t$sm = memory_get_usage();\n\t\t\t$font_family_list = $GLOBALS['MA_CustomFonts']->get_font_families();\n\t\t\t$et = microtime(true);\n\t\t\t$em = memory_get_usage();\n\t\t\tif (WP_DEBUG && $GLOBALS['MA_CustomFonts']->timing) {error_log(sprintf('MA_CustomFonts\/%s runtime: %.5f sec, memory usage: %.2f KB', __METHOD__, $et-$st, ($em-$sm)\/1024));}\n\t\t\treturn $font_family_list;\n\t\t}\n\t}\n\tglobal $ECF_Plugin;\n\t$ECF_Plugin = new ECF_Plugin();\n}\n\nendif;","active":true,"modified":"2026-01-19 15:32:00","revision":"23"}]}