- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
171 lines
5.5 KiB
PHP
171 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\Media;
|
|
|
|
/**
|
|
* Erzeugt HTML-Source-Sets für responsive Bilder
|
|
*/
|
|
final readonly class ImageSourceSetGenerator
|
|
{
|
|
/**
|
|
* Generiert ein vollständiges Picture-Element mit Source-Tags für verschiedene Formate und Größen
|
|
*
|
|
* @param Image $image Das Originalbild mit seinen Varianten
|
|
* @param string $variantType Der Variantentyp (thumbnail, gallery, hero)
|
|
* @param array $attributes Zusätzliche Attribute für das img-Tag (alt, class, etc.)
|
|
* @return string HTML Picture-Element
|
|
*/
|
|
public function generatePictureElement(Image $image, string $variantType = 'gallery', array $attributes = []): string
|
|
{
|
|
$sources = [];
|
|
|
|
// Varianten nach Format gruppieren
|
|
$variantsByFormat = $this->groupVariantsByFormat($image, $variantType);
|
|
|
|
// Source-Tags für jedes Format generieren
|
|
foreach (['avif', 'webp', 'jpeg'] as $format) {
|
|
if (isset($variantsByFormat[$format])) {
|
|
$sources[] = $this->generateSourceElement($variantsByFormat[$format], ImageFormat::from($format));
|
|
}
|
|
}
|
|
|
|
// Fallback img-Tag
|
|
$fallbackImage = $this->getFallbackImage($variantsByFormat, $image);
|
|
$attributes['src'] = $fallbackImage->getUrl();
|
|
$attributes['width'] = $fallbackImage->width;
|
|
$attributes['height'] = $fallbackImage->height;
|
|
|
|
if (! isset($attributes['alt'])) {
|
|
$attributes['alt'] = '';
|
|
}
|
|
|
|
$sources[] = $this->generateImgTag($attributes);
|
|
|
|
return '<picture>' . implode('', $sources) . '</picture>';
|
|
}
|
|
|
|
/**
|
|
* Generiert einen einfachen source-Tag mit srcset
|
|
*
|
|
* @param array $variants Bildvarianten für ein bestimmtes Format
|
|
* @param ImageFormat $format Das Bildformat
|
|
* @return string HTML source-Element
|
|
*/
|
|
private function generateSourceElement(array $variants, ImageFormat $format): string
|
|
{
|
|
$srcset = [];
|
|
$sizes = [];
|
|
|
|
// Nach Größe sortieren (klein nach groß)
|
|
usort($variants, function (ImageVariant $a, ImageVariant $b) {
|
|
return $a->width <=> $b->width;
|
|
});
|
|
|
|
foreach ($variants as $variant) {
|
|
// URL und Breite für srcset
|
|
$srcset[] = $variant->getUrl() . ' ' . $variant->width . 'w';
|
|
|
|
// Sizes basierend auf Breakpoints
|
|
$size = ImageSize::from($variant->size);
|
|
$breakpoint = $size->getBreakpoint();
|
|
|
|
if ($breakpoint !== null) {
|
|
$sizes[] = "(max-width: {$breakpoint}px) {$variant->width}px";
|
|
}
|
|
}
|
|
|
|
// Größte Variante als Standardgröße hinzufügen
|
|
$sizes[] = $variants[count($variants) - 1]->width . 'px';
|
|
|
|
return sprintf(
|
|
'<source type="%s" srcset="%s" sizes="%s">',
|
|
$format->getMimeType(),
|
|
implode(', ', $srcset),
|
|
implode(', ', $sizes)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Erzeugt einen img-Tag mit den angegebenen Attributen
|
|
*
|
|
* @param array $attributes HTML-Attribute für das img-Tag
|
|
* @return string HTML img-Element
|
|
*/
|
|
private function generateImgTag(array $attributes): string
|
|
{
|
|
$htmlAttributes = [];
|
|
|
|
foreach ($attributes as $name => $value) {
|
|
$htmlAttributes[] = sprintf('%s="%s"', $name, htmlspecialchars((string)$value));
|
|
}
|
|
|
|
return '<img ' . implode(' ', $htmlAttributes) . '>';
|
|
}
|
|
|
|
/**
|
|
* Gruppiert Bildvarianten nach Format
|
|
*
|
|
* @param Image $image Das Originalbild
|
|
* @param string $variantType Der Variantentyp
|
|
* @return array Varianten gruppiert nach Format
|
|
*/
|
|
private function groupVariantsByFormat(Image $image, string $variantType): array
|
|
{
|
|
$variantsByFormat = [];
|
|
|
|
// Check if variants are loaded to avoid uninitialized readonly property error
|
|
if (! isset($image->variants)) {
|
|
return [];
|
|
}
|
|
|
|
foreach ($image->variants as $variant) {
|
|
if ($variant->variantType === $variantType) {
|
|
$variantsByFormat[$variant->format][] = $variant;
|
|
}
|
|
}
|
|
|
|
return $variantsByFormat;
|
|
}
|
|
|
|
/**
|
|
* Wählt das beste Fallback-Bild aus
|
|
*
|
|
* @param array $variantsByFormat Varianten gruppiert nach Format
|
|
* @param Image $image Das Originalbild
|
|
* @return ImageVariant Die beste Fallback-Variante
|
|
*/
|
|
private function getFallbackImage(array $variantsByFormat, Image $image): ImageVariant
|
|
{
|
|
// Bevorzugen JPEG als Fallback
|
|
if (! empty($variantsByFormat['jpeg'])) {
|
|
// Mittlere Größe als Fallback verwenden
|
|
$variants = $variantsByFormat['jpeg'];
|
|
|
|
return $variants[min(1, count($variants) - 1)];
|
|
}
|
|
|
|
// Alternativen, falls kein JPEG verfügbar
|
|
foreach (['webp', 'avif'] as $format) {
|
|
if (! empty($variantsByFormat[$format])) {
|
|
return $variantsByFormat[$format][0];
|
|
}
|
|
}
|
|
|
|
// Wenn keine Varianten gefunden wurden, das Originalbild verwenden
|
|
return new ImageVariant(
|
|
imageId: (string) $image->ulid,
|
|
variantType: 'original',
|
|
size: 'original',
|
|
format: pathinfo($image->filename, PATHINFO_EXTENSION),
|
|
mimeType: $image->mimeType,
|
|
fileSize: $image->fileSize,
|
|
width: $image->width,
|
|
height: $image->height,
|
|
filename: $image->filename,
|
|
path: $image->path->toString(),
|
|
);
|
|
}
|
|
}
|