diff --git a/native/wordpress/maple-fonts-wp/assets/admin.css b/native/wordpress/maple-fonts-wp/assets/admin.css
index 1a923f3..f250843 100644
--- a/native/wordpress/maple-fonts-wp/assets/admin.css
+++ b/native/wordpress/maple-fonts-wp/assets/admin.css
@@ -520,6 +520,33 @@
margin: 12px 0;
}
+/* Settings Toggle */
+.mlf-setting-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 16px;
+ background: #fff;
+ border: 1px solid #dcdcde;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: normal !important;
+ transition: background-color 0.15s ease;
+}
+
+.mlf-setting-toggle:hover {
+ background: #f6f7f7;
+}
+
+.mlf-setting-toggle input[type="checkbox"] {
+ margin: 0;
+}
+
+.mlf-settings-section .description {
+ margin-top: 8px;
+ max-width: 600px;
+}
+
/* Loading State */
.mlf-loading {
opacity: 0.6;
diff --git a/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php b/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php
index 725df74..2236e9a 100644
--- a/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php
+++ b/native/wordpress/maple-fonts-wp/includes/class-mlf-admin-page.php
@@ -23,6 +23,39 @@ class MLF_Admin_Page {
// Empty constructor - class is instantiated for rendering
}
+ /**
+ * Handle settings form submission.
+ */
+ private function handle_settings_save() {
+ if (!isset($_POST['mlf_save_settings'])) {
+ return;
+ }
+
+ // Verify nonce
+ if (!isset($_POST['mlf_settings_nonce']) || !wp_verify_nonce($_POST['mlf_settings_nonce'], 'mlf_save_settings')) {
+ add_settings_error('mlf_settings', 'nonce_error', __('Security check failed.', 'maple-local-fonts'), 'error');
+ return;
+ }
+
+ // Verify capability
+ $capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
+ if (!current_user_can($capability)) {
+ return;
+ }
+
+ // Save compatibility mode setting
+ $compatibility_mode = isset($_POST['mlf_compatibility_mode']) && $_POST['mlf_compatibility_mode'] === '1';
+ update_option('mlf_compatibility_mode', $compatibility_mode);
+
+ // Clear font caches to apply new URL format
+ delete_transient('mlf_imported_fonts_list');
+ if (class_exists('WP_Theme_JSON_Resolver')) {
+ WP_Theme_JSON_Resolver::clean_cached_data();
+ }
+
+ add_settings_error('mlf_settings', 'settings_saved', __('Settings saved.', 'maple-local-fonts'), 'success');
+ }
+
/**
* Render the admin page.
*/
@@ -32,11 +65,15 @@ class MLF_Admin_Page {
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'maple-local-fonts'));
}
+ // Handle settings save
+ $this->handle_settings_save();
+
$registry = new MLF_Font_Registry();
$installed_fonts = $registry->get_imported_fonts();
?>
+
@@ -162,6 +199,28 @@ class MLF_Admin_Page {
+
+
+
diff --git a/native/wordpress/maple-fonts-wp/includes/class-mlf-font-downloader.php b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-downloader.php
index d83f15d..46e74b0 100644
--- a/native/wordpress/maple-fonts-wp/includes/class-mlf-font-downloader.php
+++ b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-downloader.php
@@ -170,6 +170,7 @@ class MLF_Font_Downloader {
[300, 400, 500, 600, 700], // Common range
[400, 500, 600, 700], // Without light
[400, 700], // Just regular and bold
+ [400], // Single weight (display fonts)
];
$css = null;
@@ -227,7 +228,8 @@ class MLF_Font_Downloader {
$family = str_replace(' ', '+', $font_name);
// Try different weight ranges - fonts support varying ranges
- $weight_ranges = ['300..900', '300..800', '300..700', '400..700'];
+ // Also try single weight 400 for fonts that only have one weight
+ $weight_ranges = ['300..900', '300..800', '300..700', '400..700', '400'];
foreach ($weight_ranges as $range) {
if ($italic) {
diff --git a/native/wordpress/maple-fonts-wp/includes/class-mlf-font-registry.php b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-registry.php
index 171c1f8..d43fee8 100644
--- a/native/wordpress/maple-fonts-wp/includes/class-mlf-font-registry.php
+++ b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-registry.php
@@ -144,6 +144,10 @@ class MLF_Font_Registry {
return new WP_Error('not_ours', 'Cannot delete fonts not imported by this plugin');
}
+ // Get font info before deletion for cleanup
+ $settings = json_decode($family->post_content, true);
+ $font_slug = $settings['slug'] ?? $family->post_name;
+
// Get font faces (children)
$faces = get_children([
'post_parent' => $family_id,
@@ -154,11 +158,11 @@ class MLF_Font_Registry {
// Delete font face files and posts
foreach ($faces as $face) {
- $settings = json_decode($face->post_content, true);
+ $face_settings = json_decode($face->post_content, true);
- if (isset($settings['src'])) {
+ if (isset($face_settings['src'])) {
// Convert file:. URL to path
- $src = $settings['src'];
+ $src = $face_settings['src'];
$src = str_replace('file:./', '', $src);
$file_path = trailingslashit($font_dir['path']) . basename($src);
@@ -176,12 +180,83 @@ class MLF_Font_Registry {
// Delete family post
wp_delete_post($family_id, true);
+ // Clean up global styles references to this font
+ $this->remove_font_from_global_styles($font_slug);
+
// Clear all font-related caches
$this->clear_font_caches();
return true;
}
+ /**
+ * Remove references to a deleted font from global styles.
+ *
+ * This prevents block errors when a font is deleted but still referenced.
+ *
+ * @param string $font_slug The font slug to remove.
+ */
+ private function remove_font_from_global_styles($font_slug) {
+ // Get the global styles post for the current theme
+ $global_styles = get_posts([
+ 'post_type' => 'wp_global_styles',
+ 'posts_per_page' => 1,
+ 'post_status' => 'publish',
+ 'tax_query' => [
+ [
+ 'taxonomy' => 'wp_theme',
+ 'field' => 'name',
+ 'terms' => get_stylesheet(),
+ ],
+ ],
+ ]);
+
+ if (empty($global_styles)) {
+ return;
+ }
+
+ $global_styles_post = $global_styles[0];
+ $content = json_decode($global_styles_post->post_content, true);
+
+ if (empty($content)) {
+ return;
+ }
+
+ $modified = false;
+
+ // Helper function to recursively remove font references
+ $remove_font_refs = function (&$data) use ($font_slug, &$modified, &$remove_font_refs) {
+ if (!is_array($data)) {
+ return;
+ }
+
+ foreach ($data as $key => &$value) {
+ // Check for fontFamily references using the slug pattern
+ if ($key === 'fontFamily' && is_string($value)) {
+ // WordPress uses format like: var(--wp--preset--font-family--font-slug)
+ if (strpos($value, '--font-family--' . $font_slug) !== false) {
+ unset($data[$key]);
+ $modified = true;
+ }
+ }
+
+ // Check for typography.fontFamily in element/block styles
+ if (is_array($value)) {
+ $remove_font_refs($value);
+ }
+ }
+ };
+
+ $remove_font_refs($content);
+
+ if ($modified) {
+ wp_update_post([
+ 'ID' => $global_styles_post->ID,
+ 'post_content' => wp_json_encode($content),
+ ]);
+ }
+ }
+
/**
* Clear all font-related caches.
*/
@@ -320,6 +395,82 @@ class MLF_Font_Registry {
return $result;
}
+ /**
+ * Get all fonts imported by this plugin, including actual filenames.
+ *
+ * This method includes the actual filename stored in the database,
+ * which is needed to correctly reference variable vs static font files.
+ *
+ * @return array Array of font data with filenames.
+ */
+ public function get_imported_fonts_with_src() {
+ $fonts = get_posts([
+ 'post_type' => 'wp_font_family',
+ 'posts_per_page' => 100,
+ 'post_status' => 'publish',
+ 'meta_key' => '_mlf_imported',
+ 'meta_value' => '1',
+ ]);
+
+ if (empty($fonts)) {
+ return [];
+ }
+
+ // Collect all font IDs for batch query
+ $font_ids = wp_list_pluck($fonts, 'ID');
+
+ // Single query to get ALL font faces for ALL fonts
+ $all_faces = get_posts([
+ 'post_type' => 'wp_font_face',
+ 'posts_per_page' => 1000,
+ 'post_status' => 'publish',
+ 'post_parent__in' => $font_ids,
+ ]);
+
+ // Group faces by parent font ID
+ $faces_by_font = [];
+ foreach ($all_faces as $face) {
+ $parent_id = $face->post_parent;
+ if (!isset($faces_by_font[$parent_id])) {
+ $faces_by_font[$parent_id] = [];
+ }
+ $faces_by_font[$parent_id][] = $face;
+ }
+
+ $result = [];
+
+ foreach ($fonts as $font) {
+ $settings = json_decode($font->post_content, true);
+
+ // Get variants from pre-fetched data
+ $faces = $faces_by_font[$font->ID] ?? [];
+
+ $variants = [];
+ foreach ($faces as $face) {
+ $face_settings = json_decode($face->post_content, true);
+
+ // Extract filename from src (format: "file:./filename.woff2")
+ $src = $face_settings['src'] ?? '';
+ $filename = str_replace('file:./', '', $src);
+
+ $variants[] = [
+ 'weight' => $face_settings['fontWeight'] ?? '400',
+ 'style' => $face_settings['fontStyle'] ?? 'normal',
+ 'filename' => $filename,
+ ];
+ }
+
+ $result[] = [
+ 'id' => $font->ID,
+ 'name' => $settings['name'] ?? $font->post_title,
+ 'slug' => $settings['slug'] ?? $font->post_name,
+ 'variants' => $variants,
+ ];
+ }
+
+ return $result;
+ }
+
/**
* Validate that a path is within the WordPress fonts directory.
*
diff --git a/native/wordpress/maple-fonts-wp/includes/class-mlf-font-server.php b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-server.php
new file mode 100644
index 0000000..eab2c36
--- /dev/null
+++ b/native/wordpress/maple-fonts-wp/includes/class-mlf-font-server.php
@@ -0,0 +1,182 @@
+[a-zA-Z0-9_\-]+\.woff2)', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'serve_font'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'filename' => [
+ 'required' => true,
+ 'validate_callback' => [$this, 'validate_filename'],
+ ],
+ ],
+ ]);
+
+ // Serve CSS file (like Google Fonts does)
+ register_rest_route('mlf/v1', '/css', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'serve_css'],
+ 'permission_callback' => '__return_true',
+ ]);
+ }
+
+ /**
+ * Validate font filename.
+ *
+ * @param string $filename The filename to validate.
+ * @return bool True if valid.
+ */
+ public function validate_filename($filename) {
+ // Only allow alphanumeric, underscore, hyphen, and .woff2 extension
+ if (!preg_match('/^[a-zA-Z0-9_\-]+\.woff2$/', $filename)) {
+ return false;
+ }
+
+ // Prevent directory traversal
+ if (strpos($filename, '..') !== false || strpos($filename, '/') !== false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Serve a font file with proper headers.
+ *
+ * @param WP_REST_Request $request The request object.
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function serve_font($request) {
+ $filename = $request->get_param('filename');
+
+ // Get font directory
+ $font_dir = wp_get_font_dir();
+ $file_path = trailingslashit($font_dir['path']) . $filename;
+
+ // Validate file exists and is within fonts directory
+ $real_path = realpath($file_path);
+ $fonts_path = realpath($font_dir['path']);
+
+ if (!$real_path || !$fonts_path || strpos($real_path, $fonts_path) !== 0) {
+ return new WP_Error('not_found', 'Font not found', ['status' => 404]);
+ }
+
+ if (!file_exists($real_path)) {
+ return new WP_Error('not_found', 'Font not found', ['status' => 404]);
+ }
+
+ // Verify it's a woff2 file
+ if (pathinfo($real_path, PATHINFO_EXTENSION) !== 'woff2') {
+ return new WP_Error('invalid_type', 'Invalid file type', ['status' => 400]);
+ }
+
+ // Read file content
+ $content = file_get_contents($real_path);
+
+ if ($content === false) {
+ return new WP_Error('read_error', 'Could not read font file', ['status' => 500]);
+ }
+
+ // Verify WOFF2 magic bytes
+ if (substr($content, 0, 4) !== 'wOF2') {
+ return new WP_Error('invalid_format', 'Invalid font format', ['status' => 400]);
+ }
+
+ // Send font with proper headers
+ $response = new WP_REST_Response($content);
+ $response->set_status(200);
+ $response->set_headers([
+ 'Content-Type' => 'font/woff2',
+ 'Content-Length' => strlen($content),
+ 'Access-Control-Allow-Origin' => '*',
+ 'Access-Control-Allow-Methods' => 'GET, OPTIONS',
+ 'Cross-Origin-Resource-Policy' => 'cross-origin',
+ 'Cache-Control' => 'public, max-age=31536000, immutable',
+ 'Vary' => 'Accept-Encoding',
+ ]);
+
+ return $response;
+ }
+
+ /**
+ * Serve CSS file with @font-face rules (mimics Google Fonts).
+ *
+ * @param WP_REST_Request $request The request object.
+ * @return WP_REST_Response Response with CSS content.
+ */
+ public function serve_css($request) {
+ $registry = new MLF_Font_Registry();
+ $fonts = $registry->get_imported_fonts();
+
+ if (empty($fonts)) {
+ $response = new WP_REST_Response('/* No fonts installed */');
+ $response->set_headers(['Content-Type' => 'text/css; charset=UTF-8']);
+ return $response;
+ }
+
+ $css = "/* Maple Local Fonts - Generated CSS */\n\n";
+
+ foreach ($fonts as $font) {
+ $font_slug = sanitize_title($font['name']);
+
+ foreach ($font['variants'] as $variant) {
+ $weight = $variant['weight'];
+ $style = $variant['style'];
+
+ // Build filename
+ if (strpos($weight, ' ') !== false) {
+ $filename = sprintf('%s_%s_variable.woff2', $font_slug, $style);
+ } else {
+ $filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
+ }
+
+ // Use REST API URL for font file
+ $font_url = rest_url('mlf/v1/font/' . $filename);
+
+ // Generate @font-face rule (format like Google Fonts)
+ $css .= "/* {$style} {$weight} */\n";
+ $css .= "@font-face {\n";
+ $css .= " font-family: '{$font['name']}';\n";
+ $css .= " font-style: {$style};\n";
+ $css .= " font-weight: {$weight};\n";
+ $css .= " font-display: swap;\n";
+ $css .= " src: url({$font_url}) format('woff2');\n";
+ $css .= "}\n\n";
+ }
+ }
+
+ $response = new WP_REST_Response($css);
+ $response->set_status(200);
+ $response->set_headers([
+ 'Content-Type' => 'text/css; charset=UTF-8',
+ 'Access-Control-Allow-Origin' => '*',
+ 'Cache-Control' => 'public, max-age=86400',
+ ]);
+
+ return $response;
+ }
+}
diff --git a/native/wordpress/maple-fonts-wp/maple-local-fonts.php b/native/wordpress/maple-fonts-wp/maple-local-fonts.php
index 82cc2fe..ebb087e 100644
--- a/native/wordpress/maple-fonts-wp/maple-local-fonts.php
+++ b/native/wordpress/maple-fonts-wp/maple-local-fonts.php
@@ -32,6 +32,20 @@ define('MLF_MAX_CSS_SIZE', 512 * 1024); // 512KB max CSS response
define('MLF_MAX_FONT_FILE_SIZE', 5 * 1024 * 1024); // 5MB max font file
define('MLF_MAX_FONT_FACES', 20); // Max font faces per import (9 weights × 2 styles + buffer)
+/**
+ * Ensure WOFF2 MIME type is registered (fixes Safari font loading).
+ *
+ * @param array $mimes Existing MIME types.
+ * @return array Modified MIME types.
+ */
+function mlf_add_woff2_mime_type($mimes) {
+ $mimes['woff2'] = 'font/woff2';
+ $mimes['woff'] = 'font/woff';
+ return $mimes;
+}
+add_filter('mime_types', 'mlf_add_woff2_mime_type');
+
+
/**
* Check WordPress version on activation.
*/
@@ -50,9 +64,46 @@ function mlf_activate() {
if (!file_exists($font_dir['path'])) {
wp_mkdir_p($font_dir['path']);
}
+
+ // Create .htaccess for proper MIME types and CORS (Apache servers)
+ $htaccess_path = trailingslashit($font_dir['path']) . '.htaccess';
+ if (!file_exists($htaccess_path)) {
+ $htaccess_content = "# Maple Local Fonts - Font MIME types and CORS\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= " AddType font/woff2 .woff2\n";
+ $htaccess_content .= " AddType font/woff .woff\n";
+ $htaccess_content .= "\n\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= " \n";
+ $htaccess_content .= " Header set Access-Control-Allow-Origin \"*\"\n";
+ $htaccess_content .= " \n";
+ $htaccess_content .= "\n";
+
+ global $wp_filesystem;
+ if (empty($wp_filesystem)) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ if ($wp_filesystem) {
+ $wp_filesystem->put_contents($htaccess_path, $htaccess_content, FS_CHMOD_FILE);
+ }
+ }
+
+ // Flush rewrite rules for fonts CSS endpoint
+ mlf_add_rewrite_rules();
+ flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'mlf_activate');
+/**
+ * Flush rewrite rules on deactivation.
+ */
+function mlf_deactivate() {
+ flush_rewrite_rules();
+}
+register_deactivation_hook(__FILE__, 'mlf_deactivate');
+
/**
* Check WordPress version on admin init (in case WP was downgraded).
*/
@@ -64,6 +115,44 @@ function mlf_check_version() {
}
add_action('admin_init', 'mlf_check_version');
+/**
+ * Ensure fonts directory htaccess exists (for MIME types and CORS).
+ */
+function mlf_ensure_htaccess() {
+ $font_dir = wp_get_font_dir();
+ $htaccess_path = trailingslashit($font_dir['path']) . '.htaccess';
+
+ // Always update htaccess to ensure latest headers
+ $htaccess_content = "# Maple Local Fonts - Font MIME types and CORS\n";
+ $htaccess_content .= "# Required for cross-browser font loading including iOS Safari\n\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= " AddType font/woff2 .woff2\n";
+ $htaccess_content .= " AddType font/woff .woff\n";
+ $htaccess_content .= "\n\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= " \n";
+ $htaccess_content .= " Header set Access-Control-Allow-Origin \"*\"\n";
+ $htaccess_content .= " Header set Access-Control-Allow-Methods \"GET, OPTIONS\"\n";
+ $htaccess_content .= " Header set Access-Control-Allow-Headers \"Origin, Content-Type\"\n";
+ $htaccess_content .= " Header set Cross-Origin-Resource-Policy \"cross-origin\"\n";
+ $htaccess_content .= " Header set Timing-Allow-Origin \"*\"\n";
+ $htaccess_content .= " \n";
+ $htaccess_content .= "\n";
+
+ if (file_exists($font_dir['path'])) {
+ global $wp_filesystem;
+ if (empty($wp_filesystem)) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ if ($wp_filesystem) {
+ $wp_filesystem->put_contents($htaccess_path, $htaccess_content, FS_CHMOD_FILE);
+ }
+ }
+}
+add_action('admin_init', 'mlf_ensure_htaccess');
+
/**
* Display version notice.
*/
@@ -133,9 +222,41 @@ add_action('plugins_loaded', 'mlf_init', 20);
function mlf_register_rest_routes() {
$controller = new MLF_Rest_Controller();
$controller->register_routes();
+
+ // Register font server routes for cross-browser compatibility
+ $font_server = new MLF_Font_Server();
+ $font_server->register_routes();
}
add_action('rest_api_init', 'mlf_register_rest_routes');
+/**
+ * Check if we should use REST API for font serving (compatibility mode).
+ *
+ * @return bool True if using REST API font serving.
+ */
+function mlf_use_rest_font_serving() {
+ return get_option('mlf_compatibility_mode', true); // Default to true for maximum compatibility
+}
+
+/**
+ * Get the URL for a font file.
+ *
+ * Uses REST API endpoint for compatibility, or direct file URL for performance.
+ *
+ * @param string $filename The font filename.
+ * @return string The font URL.
+ */
+function mlf_get_font_url($filename) {
+ if (mlf_use_rest_font_serving()) {
+ // Use REST API endpoint (guaranteed proper headers)
+ return rest_url('mlf/v1/font/' . $filename);
+ } else {
+ // Use direct file URL (faster but depends on server config)
+ $font_dir = wp_get_font_dir();
+ return trailingslashit($font_dir['url']) . $filename;
+ }
+}
+
/**
* Get the required capability for managing fonts.
*
@@ -154,20 +275,24 @@ function mlf_get_capability() {
/**
* Add imported fonts to the theme.json typography settings.
*
- * This makes fonts appear in the Site Editor typography dropdown.
+ * This makes fonts appear in the Site Editor typography dropdown
+ * and generates the @font-face CSS for the frontend.
*
* @param WP_Theme_JSON_Data $theme_json The theme.json data.
* @return WP_Theme_JSON_Data Modified theme.json data.
*/
function mlf_add_fonts_to_theme_json($theme_json) {
$registry = new MLF_Font_Registry();
- $fonts = $registry->get_imported_fonts();
+ $fonts = $registry->get_imported_fonts_with_src();
if (empty($fonts)) {
return $theme_json;
}
+ // Build our font families
$font_families = [];
+ $font_dir = wp_get_font_dir();
+ $font_base_url = trailingslashit($font_dir['url']);
foreach ($fonts as $font) {
$font_faces = [];
@@ -175,24 +300,17 @@ function mlf_add_fonts_to_theme_json($theme_json) {
foreach ($font['variants'] as $variant) {
$weight = $variant['weight'];
$style = $variant['style'];
+ $filename = $variant['filename'];
- // Build filename based on our naming convention
- $font_slug = sanitize_title($font['name']);
- if (strpos($weight, ' ') !== false) {
- // Variable font
- $filename = sprintf('%s_%s_variable.woff2', $font_slug, $style);
- } else {
- $filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
- }
-
- $font_dir = wp_get_font_dir();
- $font_url = trailingslashit($font_dir['url']) . $filename;
+ // Use direct file URL
+ $font_url = $font_base_url . $filename;
$font_faces[] = [
- 'fontFamily' => $font['name'],
- 'fontWeight' => $weight,
- 'fontStyle' => $style,
- 'src' => [$font_url],
+ 'fontFamily' => $font['name'],
+ 'fontWeight' => $weight,
+ 'fontStyle' => $style,
+ 'fontDisplay' => 'swap',
+ 'src' => [$font_url],
];
}
@@ -204,6 +322,7 @@ function mlf_add_fonts_to_theme_json($theme_json) {
];
}
+ // Use update_with to merge - WordPress handles the merging logic
$new_data = [
'version' => 2,
'settings' => [
@@ -215,7 +334,172 @@ function mlf_add_fonts_to_theme_json($theme_json) {
return $theme_json->update_with($new_data);
}
-add_filter('wp_theme_json_data_user', 'mlf_add_fonts_to_theme_json');
+add_filter('wp_theme_json_data_user', 'mlf_add_fonts_to_theme_json', 10);
+
+/**
+ * Generate @font-face CSS for imported fonts.
+ *
+ * @return string CSS content.
+ */
+function mlf_get_font_face_css() {
+ $registry = new MLF_Font_Registry();
+ $fonts = $registry->get_imported_fonts();
+
+ if (empty($fonts)) {
+ return '';
+ }
+
+ $css = '';
+
+ foreach ($fonts as $font) {
+ $font_slug = sanitize_title($font['name']);
+ // Escape font name for CSS (handle quotes and special chars)
+ $css_font_name = addcslashes($font['name'], "'\\");
+
+ foreach ($font['variants'] as $variant) {
+ $weight = $variant['weight'];
+ $style = $variant['style'];
+
+ // Build filename based on our naming convention
+ if (strpos($weight, ' ') !== false) {
+ // Variable font
+ $filename = sprintf('%s_%s_variable.woff2', $font_slug, $style);
+ } else {
+ $filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
+ }
+
+ // Get font URL (uses REST API or direct based on setting)
+ $font_url = mlf_get_font_url($filename);
+
+ // Ensure HTTPS if site uses HTTPS (important for reverse proxy setups)
+ if (is_ssl() && strpos($font_url, 'http://') === 0) {
+ $font_url = str_replace('http://', 'https://', $font_url);
+ }
+
+ $css .= "@font-face {";
+ $css .= "font-family: '{$css_font_name}';";
+ $css .= "font-style: {$style};";
+ $css .= "font-weight: {$weight};";
+ $css .= "font-display: swap;";
+ $css .= "src: url('" . esc_url($font_url) . "') format('woff2');";
+ $css .= "}\n";
+ }
+ }
+
+ return $css;
+}
+
+/**
+ * Enqueue font face CSS on frontend.
+ *
+ * Uses external CSS file just like Google Fonts does.
+ */
+function mlf_enqueue_font_faces() {
+ $registry = new MLF_Font_Registry();
+ $fonts = $registry->get_imported_fonts();
+
+ if (empty($fonts)) {
+ return;
+ }
+
+ // Load CSS from custom endpoint (like Google Fonts serves CSS)
+ wp_enqueue_style(
+ 'mlf-local-fonts',
+ home_url('/mlf-fonts.css'),
+ [],
+ null // No version string, like Google Fonts
+ );
+}
+add_action('wp_enqueue_scripts', 'mlf_enqueue_font_faces');
+
+/**
+ * Enqueue font face CSS in block editor.
+ */
+function mlf_enqueue_editor_font_faces() {
+ $registry = new MLF_Font_Registry();
+ $fonts = $registry->get_imported_fonts();
+
+ if (empty($fonts)) {
+ return;
+ }
+
+ // Load CSS from custom endpoint (like Google Fonts serves CSS)
+ wp_enqueue_style(
+ 'mlf-local-fonts-editor',
+ home_url('/mlf-fonts.css'),
+ [],
+ null
+ );
+}
+add_action('enqueue_block_editor_assets', 'mlf_enqueue_editor_font_faces');
+
+/**
+ * Add rewrite rule for fonts CSS endpoint.
+ */
+function mlf_add_rewrite_rules() {
+ add_rewrite_rule('^mlf-fonts\.css$', 'index.php?mlf_fonts_css=1', 'top');
+}
+add_action('init', 'mlf_add_rewrite_rules');
+
+/**
+ * Add query var for fonts CSS.
+ */
+function mlf_add_query_vars($vars) {
+ $vars[] = 'mlf_fonts_css';
+ return $vars;
+}
+add_filter('query_vars', 'mlf_add_query_vars');
+
+/**
+ * Handle fonts CSS request.
+ */
+function mlf_handle_fonts_css_request() {
+ if (!get_query_var('mlf_fonts_css')) {
+ return;
+ }
+
+ // Send CSS headers
+ header('Content-Type: text/css; charset=UTF-8');
+ header('Access-Control-Allow-Origin: *');
+ header('Cache-Control: public, max-age=86400');
+ header('X-Content-Type-Options: nosniff');
+
+ $registry = new MLF_Font_Registry();
+ $fonts = $registry->get_imported_fonts_with_src();
+
+ if (empty($fonts)) {
+ echo "/* No fonts installed */\n";
+ exit;
+ }
+
+ echo "/* Maple Local Fonts */\n\n";
+
+ // Use direct font file URLs
+ $font_dir = wp_get_font_dir();
+ $font_base_url = trailingslashit($font_dir['url']);
+
+ foreach ($fonts as $font) {
+ foreach ($font['variants'] as $variant) {
+ $weight = $variant['weight'];
+ $style = $variant['style'];
+
+ // Use the actual filename from database (stored in src)
+ $filename = $variant['filename'];
+ $file_url = $font_base_url . $filename;
+
+ echo "@font-face {\n";
+ echo " font-family: '{$font['name']}';\n";
+ echo " font-style: {$style};\n";
+ echo " font-weight: {$weight};\n";
+ echo " font-display: swap;\n";
+ echo " src: url({$file_url}) format('woff2');\n";
+ echo "}\n\n";
+ }
+ }
+
+ exit;
+}
+add_action('template_redirect', 'mlf_handle_fonts_css_request');
/**
* Register admin menu.