<?php
declare(strict_types=1);

/**
 * Download the web fonts Quaeriva ships and write the local stylesheets.
 *
 * The site used to link fonts.googleapis.com on every page, which sends every
 * visitor's IP and user agent to a third party before the page has rendered --
 * the exposure German courts have already ruled on, and the one docs/PLAN-THEME.md
 * said a webfont must not create. Serving the files from the site's own origin
 * removes the request entirely and works offline.
 *
 * Run this only when a font stack changes. The output is committed and shipped;
 * an installed site never calls out.
 *
 *   php bin/fetch-fonts.php
 */

if (PHP_SAPI !== 'cli') {
    http_response_code(404);
    exit;
}

require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'bootstrap.php';
app_bootstrap();

/** Families per stack, in the order the CSS font-family list expects them. */
const STACKS = [
    // Cairo and Noto Kufi cover Arabic themselves, so the Noto Sans Arabic
    // fallback each one used to load was 193 KB of duplicate coverage. Inter has
    // no Arabic at all and borrows Cairo's, which is 30 KB against Noto Sans
    // Arabic's 162 KB for the same job.
    'cairo' => ['Cairo:wght@400..900'],
    'inter' => ['Inter:wght@400..900', 'Cairo:wght@400..900'],
    'noto_kufi' => ['Noto+Kufi+Arabic:wght@400..900'],
];

/**
 * Only the subsets this project serves. Latin-ext, Cyrillic, Greek and
 * Vietnamese would roughly double the payload for characters an Arabic/English
 * Q&A site does not set.
 */
const SUBSETS = ['arabic', 'latin'];

// Google returns woff2 only to a browser that advertises support for it.
const 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';

$fontDirectory = app_root_path('public', 'assets', 'fonts');
if (!is_dir($fontDirectory) && !mkdir($fontDirectory, 0755, true) && !is_dir($fontDirectory)) {
    fwrite(STDERR, "Could not create public/assets/fonts.\n");
    exit(1);
}

function fetch(string $url): string
{
    $context = stream_context_create([
        'http' => ['header' => 'User-Agent: ' . USER_AGENT, 'timeout' => 30],
        'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
    ]);
    $body = @file_get_contents($url, false, $context);

    if (!is_string($body) || $body === '') {
        fwrite(STDERR, 'Failed to fetch ' . $url . "\n");
        exit(1);
    }

    return $body;
}

$totalBytes = 0;

$seen = [];

foreach (STACKS as $stack => $families) {
    $rules = [];

    foreach ($families as $family) {
        $css = fetch('https://fonts.googleapis.com/css2?family=' . $family . '&display=swap');

        // Each block is preceded by a /* subset */ comment; that comment is the
        // only thing naming the subset, so the split has to keep it.
        preg_match_all('~/\*\s*([a-z0-9-]+)\s*\*/\s*(@font-face\s*\{[^}]*\})~i', $css, $matches, PREG_SET_ORDER);

        foreach ($matches as [, $subset, $block]) {
            if (!in_array(strtolower($subset), SUBSETS, true)) {
                continue;
            }

            if (preg_match('~url\((https://fonts\.gstatic\.com/[^)]+\.woff2)\)~', $block, $urlMatch) !== 1) {
                continue;
            }

            $remote = $urlMatch[1];
            $name = strtolower(explode(':', str_replace('+', '-', $family))[0]) . '-' . strtolower($subset) . '.woff2';

            if (!isset($seen[$name])) {
                $binary = fetch($remote);
                file_put_contents($fontDirectory . DIRECTORY_SEPARATOR . $name, $binary);
                $totalBytes += strlen($binary);
                $seen[$name] = true;
                printf("  %-34s %6.1f KB\n", $name, strlen($binary) / 1024);
            }

            $rules[] = str_replace($remote, $name, $block);
        }
    }

    $header = "/* Generated by bin/fetch-fonts.php. Served from this site's own origin: no request leaves the visitor's browser. */\n";
    file_put_contents(
        $fontDirectory . DIRECTORY_SEPARATOR . $stack . '.css',
        $header . implode("\n", $rules) . "\n"
    );
    printf("%s.css: %d rules\n", $stack, count($rules));
}

printf("\nTotal font payload: %.1f KB\n", $totalBytes / 1024);
