Files
fotospiel-app/app/Http/Controllers/Api/Tenant/FontController.php
Codex Agent 9bde8f3f32 Neue Branding-Page und Gäste-PWA reagiert nun auf Branding-Einstellungen vom event-admin. Implemented local Google Fonts pipeline and admin UI selects for branding and invites.
- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
    exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
  - Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
    custom override) that auto-load selected fonts.
  - Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
  - New tests cover font sync command and font manifest API.

  Tests run: php artisan test --filter=Fonts --testsuite=Feature.
  Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
  untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
2025-11-25 19:31:52 +01:00

116 lines
3.9 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
class FontController extends Controller
{
public function index(): JsonResponse
{
$fonts = Cache::remember('fonts:manifest', 60, function () {
$path = public_path('fonts/google/manifest.json');
if (! File::exists($path)) {
return $this->fallbackFonts();
}
$raw = json_decode(File::get($path), true);
$entries = Arr::get($raw, 'fonts', []);
if (! is_array($entries)) {
return $this->fallbackFonts();
}
$fonts = array_values(array_filter(array_map(function ($font) {
if (! is_array($font) || ! isset($font['family'])) {
return null;
}
$variants = array_values(array_filter(array_map(function ($variant) {
if (! is_array($variant) || ! isset($variant['url'])) {
return null;
}
return [
'variant' => $variant['variant'] ?? null,
'weight' => isset($variant['weight']) ? (int) $variant['weight'] : 400,
'style' => $variant['style'] ?? 'normal',
'url' => $variant['url'],
];
}, $font['variants'] ?? [])));
if (! count($variants)) {
return null;
}
return [
'family' => (string) $font['family'],
'category' => $font['category'] ?? null,
'variants' => $variants,
];
}, $entries)));
$merged = $this->mergeFallbackFonts($fonts);
return $merged;
});
return response()->json(['data' => $fonts]);
}
private function fallbackFonts(): array
{
return [
[
'family' => 'Montserrat',
'category' => 'sans-serif',
'variants' => [
['variant' => 'regular', 'weight' => 400, 'style' => 'normal', 'url' => '/fonts/Montserrat-Regular.ttf'],
['variant' => '700', 'weight' => 700, 'style' => 'normal', 'url' => '/fonts/Montserrat-Bold.ttf'],
],
],
[
'family' => 'Lora',
'category' => 'serif',
'variants' => [
['variant' => 'regular', 'weight' => 400, 'style' => 'normal', 'url' => '/fonts/Lora-Regular.ttf'],
['variant' => '700', 'weight' => 700, 'style' => 'normal', 'url' => '/fonts/Lora-Bold.ttf'],
],
],
[
'family' => 'Playfair Display',
'category' => 'serif',
'variants' => [
['variant' => 'regular', 'weight' => 400, 'style' => 'normal', 'url' => '/fonts/PlayfairDisplay-Regular.ttf'],
['variant' => '700', 'weight' => 700, 'style' => 'normal', 'url' => '/fonts/PlayfairDisplay-Bold.ttf'],
],
],
[
'family' => 'Great Vibes',
'category' => 'script',
'variants' => [
['variant' => 'regular', 'weight' => 400, 'style' => 'normal', 'url' => '/fonts/GreatVibes-Regular.ttf'],
],
],
];
}
private function mergeFallbackFonts(array $fonts): array
{
$existingFamilies = collect($fonts)->pluck('family')->all();
$fallbacks = $this->fallbackFonts();
foreach ($fallbacks as $font) {
if (! in_array($font['family'], $existingFamilies, true)) {
$fonts[] = $font;
}
}
return $fonts;
}
}