• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tito10047 / progressive-image-bundle / 20675022582

03 Jan 2026 08:51AM UTC coverage: 86.782%. Remained the same
20675022582

push

github

tito10047
update copyright in LICENSE.md

604 of 696 relevant lines covered (86.78%)

175.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.23
/src/Service/ResponsiveAttributeGenerator.php
1
<?php
2

3
/*
4
 * This file is part of the Progressive Image Bundle.
5
 *
6
 * (c) Jozef Môstka <https://github.com/tito10047/progressive-image-bundle>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Tito10047\ProgressiveImageBundle\Service;
12

13
use Tito10047\ProgressiveImageBundle\Dto\BreakpointAssignment;
14
use Tito10047\ProgressiveImageBundle\UrlGenerator\ResponsiveImageUrlGeneratorInterface;
15

16
final class ResponsiveAttributeGenerator {
17

18
        /**
19
         * @param $gridConfig  array{
20
         *     layouts: array<string, array{
21
         *            min_viewport: int,
22
         *            max_container: int|null
23
         *       }>,
24
         *     columns: int
25
         * }
26
         * @param $ratioConfig array<string, string>
27
         */
28
        public function __construct(
29
                private array                                $gridConfig,
30
                private array                                $ratioConfig,
31
                private readonly PreloadCollector            $preloadCollector,
32
                private ResponsiveImageUrlGeneratorInterface $urlGenerator
33
        ) {
34
        }
255✔
35

36
        /**
37
         * @return array{sizes: string, srcset: string}
38
         */
39
        public function generate(string $path, array $assignments, int $originalWidth, bool $preload, ?string $pointInterest = null): array {
40
                $assignments = $this->sortAssignments($assignments);
129✔
41

42
                $sizesParts      = [];
129✔
43
                $srcsetParts     = [];
129✔
44
                $processedWidths = [];
129✔
45

46
                foreach ($assignments as $assignment) {
129✔
47
                        $layout = $this->gridConfig['layouts'][$assignment->breakpoint] ?? null;
129✔
48
                        if (!$layout) {
129✔
49
                                continue;
×
50
                        }
51

52
                        [$pixelWidth, $sizeValue] = $this->calculateDimensions($assignment, $layout);
129✔
53

54
                        $size = $this->formatSizePart($layout['min_viewport'], $sizeValue);
129✔
55
                        $sizesParts[]   = $size;
129✔
56

57
                        $url = $this->generateUrl($path, $assignment, (int) round($pixelWidth), $originalWidth, $processedWidths, $pointInterest);
129✔
58

59
                        if ($url) {
129✔
60
                                $actualPixelWidth = (int) round($pixelWidth);
129✔
61
                                if ($preload) {
129✔
62
                                        $this->preloadCollector->add($url, 'image', 'high', "{$actualPixelWidth}w", $size);
14✔
63
                                }
64

65
                                $srcsetParts[] = $url . " {$actualPixelWidth}w";
129✔
66
                        }
67
                }
68

69
                return [
129✔
70
                        'sizes'  => implode(', ', $sizesParts),
129✔
71
                        'srcset' => implode(', ', $srcsetParts),
129✔
72
                ];
129✔
73
        }
74

75
        private function formatSizePart(int $minViewport, string $sizeValue): string {
76
                return $minViewport > 0
129✔
77
                        ? "(min-width: {$minViewport}px) {$sizeValue}"
129✔
78
                        : $sizeValue;
129✔
79
        }
80

81
        /**
82
         * @param BreakpointAssignment[] $assignments
83
         *
84
         * @return BreakpointAssignment[]
85
         */
86
        private function sortAssignments(array $assignments): array {
87
                usort($assignments, fn($a, $b) => ($this->gridConfig['layouts'][$b->breakpoint]['min_viewport'] ?? 0) <=>
129✔
88
                        ($this->gridConfig['layouts'][$a->breakpoint]['min_viewport'] ?? 0)
85✔
89
                );
129✔
90

91
                return $assignments;
129✔
92
        }
93

94
        /**
95
         * @param array{min_viewport: int, max_container: int|null} $layout
96
         *
97
         * @return array{0: float, 1: string}
98
         */
99
        private function calculateDimensions(BreakpointAssignment $assignment, array $layout): array {
100
                $totalCols    = $this->gridConfig['columns'];
129✔
101
                $maxContainer = $layout['max_container'];
129✔
102

103
                if ($maxContainer) {
129✔
104
                        // Fixný kontajner (napr. 1320px) -> šírka v px
105
                        $pixelWidth = ($assignment->columns / $totalCols) * $maxContainer;
129✔
106
                        $sizeValue  = round($pixelWidth) . 'px';
129✔
107
                } else {
108
                        // Fluid (null) -> šírka vo vw
109
                        $vwWidth   = ($assignment->columns / $totalCols) * 100;
85✔
110
                        $sizeValue = round($vwWidth) . 'vw';
85✔
111
                        // Pre výpočet URL odhadneme px šírku z nejakej rozumnej max-šírky (napr. 1920)
112
                        $pixelWidth = ($vwWidth / 100) * 1920;
85✔
113
                }
114

115
                return [$pixelWidth, $sizeValue];
129✔
116
        }
117

118
        /**
119
         * @param array<int, bool> $processedWidths
120
         *
121
         * @return string[]
122
         */
123
        private function generateUrl(
124
                string               $path,
125
                BreakpointAssignment $assignment,
126
                int                  $basePixelWidth,
127
                int                  $originalWidth,
128
                array                &$processedWidths,
129
                ?string              $pointInterest = null
130
        ): ?string {
131
                $ratio = $this->resolveRatio($assignment);
129✔
132

133
                if ($basePixelWidth > $originalWidth || isset($processedWidths[$basePixelWidth])) {
129✔
134
                        return null;
56✔
135
                }
136

137
                $targetH = $ratio ? (int) round($basePixelWidth / $ratio) : null;
129✔
138
                $url     = $this->urlGenerator->generateUrl($path, $basePixelWidth, $targetH, $pointInterest);
129✔
139

140
                $processedWidths[$basePixelWidth] = true;
129✔
141
                return $url;
129✔
142

143
        }
144

145
        private function resolveRatio(BreakpointAssignment $assignment): ?float {
146
                $ratioString = $assignment->ratio ?? null;
129✔
147
                if (!$ratioString) {
129✔
148
                        return null;
84✔
149
                }
150

151
                // Ak je to kľúč v ratioConfig, použijeme ten
152
                if (isset($this->ratioConfig[$ratioString])) {
45✔
153
                        return (float) $this->ratioConfig[$ratioString];
30✔
154
                }
155

156
                // Inak skúsime parsovať formát "3/4" alebo "3-4"
157
                if (preg_match('/^(\d+)[\/-](\d+)$/', $ratioString, $matches)) {
15✔
158
                        return (float) $matches[1] / (float) $matches[2];
15✔
159
                }
160

161
                return null;
×
162
        }
163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc