initial commit
This commit is contained in:
parent
d066133bd4
commit
e6f71e3706
55 changed files with 11928 additions and 0 deletions
707
native/wordpress/maple-fonts-wp/GOOGLE_FONTS_API.md
Normal file
707
native/wordpress/maple-fonts-wp/GOOGLE_FONTS_API.md
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
# GOOGLE_FONTS_API.md — Google Fonts Retrieval Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers how to retrieve fonts from Google Fonts CSS2 API. This is a **one-time retrieval** during admin import — after download, fonts are served locally and Google is never contacted again.
|
||||
|
||||
---
|
||||
|
||||
## The Retrieval Flow
|
||||
|
||||
```
|
||||
1. Admin enters "Open Sans" + selects weights/styles
|
||||
↓
|
||||
2. Plugin constructs Google Fonts CSS2 URL
|
||||
↓
|
||||
3. Plugin fetches CSS (contains @font-face rules with WOFF2 URLs)
|
||||
↓
|
||||
4. Plugin parses CSS to extract WOFF2 URLs
|
||||
↓
|
||||
5. Plugin downloads each WOFF2 file to wp-content/fonts/
|
||||
↓
|
||||
6. Plugin registers font with WordPress Font Library
|
||||
↓
|
||||
7. DONE - Google never contacted again
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Google Fonts CSS2 API
|
||||
|
||||
### Base URL
|
||||
|
||||
```
|
||||
https://fonts.googleapis.com/css2
|
||||
```
|
||||
|
||||
### URL Construction
|
||||
|
||||
**Pattern:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family={Font+Name}:ital,wght@{variations}&display=swap
|
||||
```
|
||||
|
||||
**Variations format:**
|
||||
```
|
||||
ital,wght@{italic},{weight};{italic},{weight};...
|
||||
```
|
||||
|
||||
Where:
|
||||
- `ital` = 0 (normal) or 1 (italic)
|
||||
- `wght` = weight (100-900)
|
||||
|
||||
### Examples
|
||||
|
||||
**Open Sans 400 normal only:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=swap
|
||||
```
|
||||
|
||||
**Open Sans 400 + 700 normal:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap
|
||||
```
|
||||
|
||||
**Open Sans 400 + 700, both normal and italic:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap
|
||||
```
|
||||
|
||||
**Breakdown of `ital,wght@0,400;0,700;1,400;1,700`:**
|
||||
- `0,400` = normal 400
|
||||
- `0,700` = normal 700
|
||||
- `1,400` = italic 400
|
||||
- `1,700` = italic 700
|
||||
|
||||
### URL Builder Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Build Google Fonts CSS2 API URL.
|
||||
*
|
||||
* @param string $font_name Font family name (e.g., "Open Sans")
|
||||
* @param array $weights Array of weights (e.g., [400, 700])
|
||||
* @param array $styles Array of styles (e.g., ['normal', 'italic'])
|
||||
* @return string Google Fonts CSS2 URL
|
||||
*/
|
||||
function mlf_build_google_fonts_url($font_name, $weights, $styles) {
|
||||
// URL-encode font name (spaces become +)
|
||||
$family = str_replace(' ', '+', $font_name);
|
||||
|
||||
// Build variations
|
||||
$variations = [];
|
||||
|
||||
// Sort for consistent URLs
|
||||
sort($weights);
|
||||
sort($styles);
|
||||
|
||||
$has_italic = in_array('italic', $styles, true);
|
||||
$has_normal = in_array('normal', $styles, true);
|
||||
|
||||
foreach ($weights as $weight) {
|
||||
if ($has_normal) {
|
||||
$variations[] = "0,{$weight}";
|
||||
}
|
||||
if ($has_italic) {
|
||||
$variations[] = "1,{$weight}";
|
||||
}
|
||||
}
|
||||
|
||||
// If only normal styles, simpler format
|
||||
if ($has_normal && !$has_italic) {
|
||||
$wght = implode(';', $weights);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:wght@{$wght}&display=swap";
|
||||
}
|
||||
|
||||
// Full format with ital axis
|
||||
$variation_string = implode(';', $variations);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:ital,wght@{$variation_string}&display=swap";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: User-Agent Requirement
|
||||
|
||||
Google Fonts returns **different file formats** based on user-agent:
|
||||
|
||||
| User-Agent | Format Returned |
|
||||
|------------|-----------------|
|
||||
| Old browser | TTF or WOFF |
|
||||
| Modern browser | WOFF2 |
|
||||
| curl (no UA) | TTF |
|
||||
|
||||
**We need WOFF2** (smallest, best compression, modern browser support).
|
||||
|
||||
### Required User-Agent
|
||||
|
||||
Use a modern Chrome user-agent:
|
||||
|
||||
```php
|
||||
$user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
```
|
||||
|
||||
### Fetch Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Fetch CSS from Google Fonts API.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to fetch
|
||||
* @param array $styles Styles to fetch
|
||||
* @return string|WP_Error CSS content or error
|
||||
*/
|
||||
function mlf_fetch_google_css($font_name, $weights, $styles) {
|
||||
$url = mlf_build_google_fonts_url($font_name, $weights, $styles);
|
||||
|
||||
// Validate URL
|
||||
if (!mlf_is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'Invalid Google Fonts URL');
|
||||
}
|
||||
|
||||
// CRITICAL: Must use modern browser user-agent to get WOFF2
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'sslverify' => true,
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error(
|
||||
'request_failed',
|
||||
'Could not connect to Google Fonts: ' . $response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status === 400) {
|
||||
return new WP_Error('font_not_found', 'Font not found on Google Fonts');
|
||||
}
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Google Fonts returned HTTP ' . $status);
|
||||
}
|
||||
|
||||
$css = wp_remote_retrieve_body($response);
|
||||
if (empty($css)) {
|
||||
return new WP_Error('empty_response', 'Empty response from Google Fonts');
|
||||
}
|
||||
|
||||
// Verify we got WOFF2 (sanity check)
|
||||
if (strpos($css, '.woff2)') === false) {
|
||||
return new WP_Error('wrong_format', 'Did not receive WOFF2 format - check user-agent');
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parsing the CSS Response
|
||||
|
||||
### Sample Response
|
||||
|
||||
Google returns CSS like this:
|
||||
|
||||
```css
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVI.woff2) format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, ...;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, ...;
|
||||
}
|
||||
```
|
||||
|
||||
### Key Observations
|
||||
|
||||
1. **Multiple @font-face blocks per weight** — Google splits fonts by unicode subset (latin, latin-ext, cyrillic, etc.)
|
||||
2. **We want the "latin" subset** — It's the base and covers most use cases
|
||||
3. **Each block has:** font-family, font-style, font-weight, src URL, unicode-range
|
||||
|
||||
### Unicode Subset Strategy
|
||||
|
||||
**Option A: Download only latin (simpler, smaller)**
|
||||
- Parse CSS, identify latin blocks, download only those
|
||||
- Good for most sites
|
||||
|
||||
**Option B: Download all subsets (more complete)**
|
||||
- Download all WOFF2 files
|
||||
- Larger but supports more languages
|
||||
|
||||
**Recommended: Option A** — Start with latin subset. Can add option for more subsets later.
|
||||
|
||||
### Parsing Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Parse Google Fonts CSS and extract font face data.
|
||||
*
|
||||
* @param string $css CSS content from Google Fonts
|
||||
* @param string $font_name Expected font family name
|
||||
* @return array|WP_Error Array of font face data or error
|
||||
*/
|
||||
function mlf_parse_google_css($css, $font_name) {
|
||||
$font_faces = [];
|
||||
|
||||
// Match all @font-face blocks
|
||||
$pattern = '/@font-face\s*\{([^}]+)\}/s';
|
||||
if (!preg_match_all($pattern, $css, $matches)) {
|
||||
return new WP_Error('parse_failed', 'Could not parse CSS - no @font-face rules found');
|
||||
}
|
||||
|
||||
foreach ($matches[1] as $block) {
|
||||
$face_data = mlf_parse_font_face_block($block);
|
||||
|
||||
if (is_wp_error($face_data)) {
|
||||
continue; // Skip malformed blocks
|
||||
}
|
||||
|
||||
// Verify font family matches (security)
|
||||
if (strcasecmp($face_data['family'], $font_name) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for weight+style combo
|
||||
$key = $face_data['weight'] . '-' . $face_data['style'];
|
||||
|
||||
// Prefer latin subset (usually comes after latin-ext)
|
||||
// Check if this is a latin block by unicode-range
|
||||
$is_latin = mlf_is_latin_subset($face_data['unicode_range']);
|
||||
|
||||
// Only store if:
|
||||
// 1. We don't have this weight/style yet, OR
|
||||
// 2. This is latin and replaces non-latin
|
||||
if (!isset($font_faces[$key]) || $is_latin) {
|
||||
$font_faces[$key] = $face_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($font_faces)) {
|
||||
return new WP_Error('no_fonts', 'No valid font faces found in CSS');
|
||||
}
|
||||
|
||||
return array_values($font_faces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single @font-face block.
|
||||
*
|
||||
* @param string $block Content inside @font-face { }
|
||||
* @return array|WP_Error Parsed data or error
|
||||
*/
|
||||
function mlf_parse_font_face_block($block) {
|
||||
$data = [];
|
||||
|
||||
// Extract font-family
|
||||
if (preg_match('/font-family:\s*[\'"]?([^;\'"]+)[\'"]?;/i', $block, $m)) {
|
||||
$data['family'] = trim($m[1]);
|
||||
} else {
|
||||
return new WP_Error('missing_family', 'Missing font-family');
|
||||
}
|
||||
|
||||
// Extract font-weight
|
||||
if (preg_match('/font-weight:\s*(\d+);/i', $block, $m)) {
|
||||
$data['weight'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_weight', 'Missing font-weight');
|
||||
}
|
||||
|
||||
// Extract font-style
|
||||
if (preg_match('/font-style:\s*(\w+);/i', $block, $m)) {
|
||||
$data['style'] = $m[1];
|
||||
} else {
|
||||
$data['style'] = 'normal'; // Default
|
||||
}
|
||||
|
||||
// Extract src URL - MUST be fonts.gstatic.com
|
||||
if (preg_match('/src:\s*url\((https:\/\/fonts\.gstatic\.com\/[^)]+\.woff2)\)/i', $block, $m)) {
|
||||
$data['url'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_src', 'Missing or invalid src URL');
|
||||
}
|
||||
|
||||
// Extract unicode-range (optional, for subset detection)
|
||||
if (preg_match('/unicode-range:\s*([^;]+);/i', $block, $m)) {
|
||||
$data['unicode_range'] = trim($m[1]);
|
||||
} else {
|
||||
$data['unicode_range'] = '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if unicode-range indicates latin subset.
|
||||
* Latin typically starts with U+0000-00FF.
|
||||
*
|
||||
* @param string $range Unicode range string
|
||||
* @return bool True if appears to be latin subset
|
||||
*/
|
||||
function mlf_is_latin_subset($range) {
|
||||
// Latin subset typically includes basic ASCII range
|
||||
// and does NOT include extended Latin (U+0100+) as primary
|
||||
|
||||
if (empty($range)) {
|
||||
return true; // Assume latin if no range specified
|
||||
}
|
||||
|
||||
// Latin subset usually starts with U+0000 and includes U+00FF
|
||||
// Latin-ext starts with U+0100
|
||||
if (preg_match('/U\+0000/', $range) && !preg_match('/^U\+0100/', $range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Downloading WOFF2 Files
|
||||
|
||||
### Download Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Download a single WOFF2 file from Google Fonts.
|
||||
*
|
||||
* @param string $url Google Fonts static URL
|
||||
* @param string $font_slug Font slug (e.g., "open-sans")
|
||||
* @param string $weight Font weight (e.g., "400")
|
||||
* @param string $style Font style (e.g., "normal")
|
||||
* @return string|WP_Error Local file path or error
|
||||
*/
|
||||
function mlf_download_font_file($url, $font_slug, $weight, $style) {
|
||||
// Validate URL is from Google
|
||||
if (!mlf_is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'URL is not from Google Fonts');
|
||||
}
|
||||
|
||||
// Build local filename
|
||||
$filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
|
||||
$filename = sanitize_file_name($filename);
|
||||
|
||||
// Get destination path
|
||||
$font_dir = wp_get_font_dir();
|
||||
$destination = trailingslashit($font_dir['path']) . $filename;
|
||||
|
||||
// Validate destination path
|
||||
if (!mlf_validate_font_path($destination)) {
|
||||
return new WP_Error('invalid_path', 'Invalid destination path');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if (!wp_mkdir_p($font_dir['path'])) {
|
||||
return new WP_Error('mkdir_failed', 'Could not create fonts directory');
|
||||
}
|
||||
|
||||
// Download file
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 30,
|
||||
'sslverify' => true,
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error(
|
||||
'download_failed',
|
||||
'Failed to download font file: ' . $response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Font download returned HTTP ' . $status);
|
||||
}
|
||||
|
||||
$content = wp_remote_retrieve_body($response);
|
||||
if (empty($content)) {
|
||||
return new WP_Error('empty_file', 'Downloaded font file is empty');
|
||||
}
|
||||
|
||||
// Verify it looks like a WOFF2 file (magic bytes: wOF2)
|
||||
if (substr($content, 0, 4) !== 'wOF2') {
|
||||
return new WP_Error('invalid_format', 'Downloaded file is not valid WOFF2');
|
||||
}
|
||||
|
||||
// Write file using WP Filesystem
|
||||
global $wp_filesystem;
|
||||
if (empty($wp_filesystem)) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
if (!$wp_filesystem->put_contents($destination, $content, FS_CHMOD_FILE)) {
|
||||
return new WP_Error('write_failed', 'Could not write font file');
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Download Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Download all font files for a font family.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to download
|
||||
* @param array $styles Styles to download
|
||||
* @return array|WP_Error Array of downloaded files or error
|
||||
*/
|
||||
function mlf_download_font_family($font_name, $weights, $styles) {
|
||||
// Fetch CSS from Google
|
||||
$css = mlf_fetch_google_css($font_name, $weights, $styles);
|
||||
if (is_wp_error($css)) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// Parse CSS to get font face data
|
||||
$font_faces = mlf_parse_google_css($css, $font_name);
|
||||
if (is_wp_error($font_faces)) {
|
||||
return $font_faces;
|
||||
}
|
||||
|
||||
// Generate slug from font name
|
||||
$font_slug = sanitize_title($font_name);
|
||||
|
||||
// Download each font file
|
||||
$downloaded = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($font_faces as $face) {
|
||||
$result = mlf_download_font_file(
|
||||
$face['url'],
|
||||
$font_slug,
|
||||
$face['weight'],
|
||||
$face['style']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
$errors[] = $result->get_error_message();
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloaded[] = [
|
||||
'path' => $result,
|
||||
'weight' => $face['weight'],
|
||||
'style' => $face['style'],
|
||||
];
|
||||
}
|
||||
|
||||
// If no files downloaded, return error
|
||||
if (empty($downloaded)) {
|
||||
return new WP_Error(
|
||||
'download_failed',
|
||||
'Could not download any font files: ' . implode(', ', $errors)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'font_name' => $font_name,
|
||||
'font_slug' => $font_slug,
|
||||
'files' => $downloaded,
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Cause | User Message |
|
||||
|-------|-------|--------------|
|
||||
| Font not found | Typo in font name | "Font not found on Google Fonts. Check the spelling." |
|
||||
| Network timeout | Slow connection | "Could not connect to Google Fonts. Please try again." |
|
||||
| Invalid format | Wrong user-agent | Internal error - should not happen |
|
||||
| Write failed | Permissions | "Could not save font files. Check directory permissions." |
|
||||
|
||||
### Error Messages (User-Friendly)
|
||||
|
||||
```php
|
||||
/**
|
||||
* Convert internal error codes to user-friendly messages.
|
||||
*
|
||||
* @param WP_Error $error The error object
|
||||
* @return string User-friendly message
|
||||
*/
|
||||
function mlf_get_user_error_message($error) {
|
||||
$code = $error->get_error_code();
|
||||
|
||||
$messages = [
|
||||
'font_not_found' => 'Font not found on Google Fonts. Please check the spelling and try again.',
|
||||
'request_failed' => 'Could not connect to Google Fonts. Please check your internet connection and try again.',
|
||||
'http_error' => 'Google Fonts returned an error. Please try again later.',
|
||||
'parse_failed' => 'Could not process the font data. The font may not be available.',
|
||||
'download_failed' => 'Could not download the font files. Please try again.',
|
||||
'write_failed' => 'Could not save font files. Please check that wp-content/fonts is writable.',
|
||||
'mkdir_failed' => 'Could not create fonts directory. Please check file permissions.',
|
||||
'invalid_path' => 'Invalid file path. Please contact support.',
|
||||
'invalid_url' => 'Invalid font URL. Please contact support.',
|
||||
];
|
||||
|
||||
return $messages[$code] ?? 'An unexpected error occurred. Please try again.';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Downloader Class
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MLF_Font_Downloader {
|
||||
|
||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
|
||||
/**
|
||||
* Download a font from Google Fonts.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to download
|
||||
* @param array $styles Styles to download
|
||||
* @return array|WP_Error Download result or error
|
||||
*/
|
||||
public function download($font_name, $weights, $styles) {
|
||||
// Validate inputs
|
||||
if (empty($font_name) || !preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
return new WP_Error('invalid_name', 'Invalid font name');
|
||||
}
|
||||
|
||||
$weights = array_intersect(array_map('absint', $weights), [100,200,300,400,500,600,700,800,900]);
|
||||
if (empty($weights)) {
|
||||
return new WP_Error('invalid_weights', 'No valid weights specified');
|
||||
}
|
||||
|
||||
$styles = array_intersect($styles, ['normal', 'italic']);
|
||||
if (empty($styles)) {
|
||||
return new WP_Error('invalid_styles', 'No valid styles specified');
|
||||
}
|
||||
|
||||
// Fetch CSS
|
||||
$css = $this->fetch_css($font_name, $weights, $styles);
|
||||
if (is_wp_error($css)) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// Parse CSS
|
||||
$font_faces = $this->parse_css($css, $font_name);
|
||||
if (is_wp_error($font_faces)) {
|
||||
return $font_faces;
|
||||
}
|
||||
|
||||
// Download files
|
||||
$font_slug = sanitize_title($font_name);
|
||||
$downloaded = $this->download_files($font_faces, $font_slug);
|
||||
if (is_wp_error($downloaded)) {
|
||||
return $downloaded;
|
||||
}
|
||||
|
||||
return [
|
||||
'font_name' => $font_name,
|
||||
'font_slug' => $font_slug,
|
||||
'files' => $downloaded,
|
||||
];
|
||||
}
|
||||
|
||||
private function build_url($font_name, $weights, $styles) {
|
||||
$family = str_replace(' ', '+', $font_name);
|
||||
sort($weights);
|
||||
|
||||
$has_italic = in_array('italic', $styles, true);
|
||||
$has_normal = in_array('normal', $styles, true);
|
||||
|
||||
if ($has_normal && !$has_italic) {
|
||||
$wght = implode(';', $weights);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:wght@{$wght}&display=swap";
|
||||
}
|
||||
|
||||
$variations = [];
|
||||
foreach ($weights as $weight) {
|
||||
if ($has_normal) {
|
||||
$variations[] = "0,{$weight}";
|
||||
}
|
||||
if ($has_italic) {
|
||||
$variations[] = "1,{$weight}";
|
||||
}
|
||||
}
|
||||
|
||||
$variation_string = implode(';', $variations);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:ital,wght@{$variation_string}&display=swap";
|
||||
}
|
||||
|
||||
private function fetch_css($font_name, $weights, $styles) {
|
||||
$url = $this->build_url($font_name, $weights, $styles);
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'sslverify' => true,
|
||||
'user-agent' => $this->user_agent,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error('request_failed', $response->get_error_message());
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status === 400) {
|
||||
return new WP_Error('font_not_found', 'Font not found');
|
||||
}
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'HTTP ' . $status);
|
||||
}
|
||||
|
||||
$css = wp_remote_retrieve_body($response);
|
||||
if (empty($css) || strpos($css, '.woff2)') === false) {
|
||||
return new WP_Error('invalid_response', 'Invalid CSS response');
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
private function parse_css($css, $font_name) {
|
||||
// Implementation as shown above
|
||||
// Returns array of font face data
|
||||
}
|
||||
|
||||
private function download_files($font_faces, $font_slug) {
|
||||
// Implementation as shown above
|
||||
// Returns array of downloaded file info
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Valid font name (Open Sans) returns CSS with WOFF2 URLs
|
||||
- [ ] Invalid font name returns appropriate error
|
||||
- [ ] Multiple weights are all downloaded
|
||||
- [ ] Italic styles are handled correctly
|
||||
- [ ] Files are saved to correct location
|
||||
- [ ] Files have correct WOFF2 magic bytes
|
||||
- [ ] Timeout handling works (test with slow connection)
|
||||
- [ ] User-agent produces WOFF2 (not TTF/WOFF)
|
||||
Loading…
Add table
Add a link
Reference in a new issue