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

tito10047 / progressive-image-bundle / 21093331125

17 Jan 2026 11:10AM UTC coverage: 90.436% (-0.5%) from 90.889%
21093331125

push

github

web-flow
Merge pull request #4 from tito10047/responsive-strategy

change rendering stratety from <img> to <picture><source><img> for better responsive strategy

268 of 332 new or added lines in 19 files covered. (80.72%)

5 existing lines in 2 files now uncovered.

851 of 941 relevant lines covered (90.44%)

318.39 hits per line

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

93.33
/src/Command/GenerateCustomCssCommand.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

12
namespace Tito10047\ProgressiveImageBundle\Command;
13

14
use Symfony\Component\Console\Attribute\AsCommand;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Style\SymfonyStyle;
20
use Symfony\Component\Filesystem\Filesystem;
21

22
#[AsCommand(
23
    name: 'progressive-image:generate-custom-css',
24
    description: 'Generates a custom Tailwind CSS file based on the bundle configuration.',
25
)]
26
class GenerateCustomCssCommand extends Command
27
{
28
    /**
29
     * @param array{
30
     *     layouts: array<string, array{
31
     *         min_viewport: int,
32
     *         max_container: int|null
33
     *     }>,
34
     *     columns: int
35
     * } $gridConfig
36
     */
37
    public function __construct(
38
        private readonly array $gridConfig,
39
        private readonly string $projectDir,
40
    ) {
41
        parent::__construct();
15✔
42
    }
43

44
    protected function configure(): void
45
    {
46
        $this
15✔
47
            ->addArgument('path', InputArgument::OPTIONAL, 'Path to the directory where the CSS file will be generated.', 'assets/styles');
15✔
48
    }
49

50
    protected function execute(InputInterface $input, OutputInterface $output): int
51
    {
52
        $io = new SymfonyStyle($input, $output);
15✔
53
        $path = $input->getArgument('path');
15✔
54

55
        if (!str_starts_with($path, '/')) {
15✔
NEW
56
            $path = $this->projectDir.'/'.$path;
×
57
        }
58

59
        $filesystem = new Filesystem();
15✔
60
        if (!$filesystem->exists($path)) {
15✔
NEW
61
            $filesystem->mkdir($path);
×
62
        }
63

64
        $filePath = $path.'/progressive-image-custom.css';
15✔
65

66
        $cssContent = $this->generateCssContent();
15✔
67

68
        $filesystem->dumpFile($filePath, $cssContent);
15✔
69

70
        $io->success(sprintf('CSS file generated at %s', $filePath));
15✔
71

72
        return Command::SUCCESS;
15✔
73
    }
74

75
    private function generateCssContent(): string
76
    {
77
        $layouts = $this->gridConfig['layouts'];
15✔
78

79
        // Sort layouts by min_viewport descending for the root .progressive-image-container nested variables
80
        $sortedLayouts = $layouts;
15✔
81
        uasort($sortedLayouts, fn ($a, $b) => $b['min_viewport'] <=> $a['min_viewport']);
15✔
82

83
        $content = "/* Progressive Image Container - Custom Breakpoints */\n";
15✔
84
        $content .= ".progressive-image-container {\n";
15✔
85
        $content .= "\tdisplay: block;\n";
15✔
86

87
        // Root width variable with fallbacks
88
        $content .= "\twidth: ".$this->generateVariableFallback($sortedLayouts, 'width').";\n";
15✔
89

90
        // Root aspect-ratio variable with fallbacks
91
        $content .= "\taspect-ratio: ".$this->generateVariableFallback($sortedLayouts, 'aspect').";\n";
15✔
92

93
        $content .= "\tposition: relative;\n";
15✔
94
        $content .= "\toverflow: hidden;\n";
15✔
95
        $content .= "}\n\n";
15✔
96

97
        // Media queries - sort by min_viewport ascending
98
        $mediaLayouts = $layouts;
15✔
99
        uasort($mediaLayouts, fn ($a, $b) => $a['min_viewport'] <=> $b['min_viewport']);
15✔
100

101
        foreach ($mediaLayouts as $name => $layout) {
15✔
102
            if (0 === $layout['min_viewport']) {
15✔
103
                continue;
15✔
104
            }
105

106
            $content .= sprintf("/* %s: %dpx */\n", $name, $layout['min_viewport']);
15✔
107
            $content .= sprintf("@media (min-width: %dpx) {\n", $layout['min_viewport']);
15✔
108
            $content .= "\t.progressive-image-container {\n";
15✔
109

110
            // For width
111
            $content .= "\t\twidth: ".$this->generateVariableFallbackForBreakpoint($name, $layouts, 'width').";\n";
15✔
112
            // For aspect
113
            $content .= "\t\taspect-ratio: ".$this->generateVariableFallbackForBreakpoint($name, $layouts, 'aspect').";\n";
15✔
114

115
            $content .= "\t}\n";
15✔
116
            $content .= "}\n\n";
15✔
117
        }
118

119
        return $content;
15✔
120
    }
121

122
    private function generateVariableFallback(array $sortedLayouts, string $type): string
123
    {
124
        $variables = [];
15✔
125
        foreach ($sortedLayouts as $name => $layout) {
15✔
126
            $suffix = 0 === $layout['min_viewport'] ? '' : '-'.$name;
15✔
127
            $variables[] = sprintf('var(--img-%s%s', $type, $suffix);
15✔
128
        }
129

130
        $result = implode(",\n\t\t\t", $variables);
15✔
131
        if (count($variables) > 1) {
15✔
132
            $result .= str_repeat(')', count($variables));
15✔
133
        } else {
NEW
134
            $result .= ')';
×
135
        }
136

137
        return $result;
15✔
138
    }
139

140
    private function generateVariableFallbackForBreakpoint(string $currentBreakpoint, array $allLayouts, string $type): string
141
    {
142
        // Get all layouts with min_viewport <= current min_viewport, sorted descending
143
        $currentMinViewport = $allLayouts[$currentBreakpoint]['min_viewport'];
15✔
144
        $filteredLayouts = array_filter($allLayouts, fn ($l) => $l['min_viewport'] <= $currentMinViewport);
15✔
145
        uasort($filteredLayouts, fn ($a, $b) => $b['min_viewport'] <=> $a['min_viewport']);
15✔
146

147
        $variables = [];
15✔
148
        foreach ($filteredLayouts as $name => $layout) {
15✔
149
            $suffix = 0 === $layout['min_viewport'] ? '' : '-'.$name;
15✔
150
            $variables[] = sprintf('var(--img-%s%s', $type, $suffix);
15✔
151
        }
152

153
        $result = implode(', ', $variables);
15✔
154
        if (count($variables) > 1) {
15✔
155
            $result .= str_repeat(')', count($variables));
15✔
156
        } else {
NEW
157
            $result .= ')';
×
158
        }
159

160
        return $result;
15✔
161
    }
162
}
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