1- <div class =" not-prose bg-base-950/80 dark:bg-base-800 border-1 dark:border-base-500/10 border-base-500/50 rounded-lg lg:rounded-xl p-1 lg:p-2 mt-4 -mx-3 lg:mx-0 shadow-sm lg:shadow-xl" >
1+ @inject IWebHostEnvironment Environment
2+
3+ <div class =" not-prose bg-base-950/80 dark:bg-base-800 border-1 dark:border-base-500/10 border-base-500/50 rounded-lg lg:rounded-xl p-1 lg:p-2 mt-4 -mx-3 lg:mx-0 shadow-sm lg:shadow-xl" >
24 <div class =" not-prose flex flex-col rounded-lg lg:rounded-xl" >
35 <!-- Terminal buttons -->
46 <div class =" flex justify-end-safe gap-2 py-2 px-4" >
79 <div class =" w-2 h-2 lg:w-3 lg:h-3 rounded-sm bg-accent-200/90 dark:bg-accent-500/60 border border-accent-900/50 dark:border-accent-600" ></div >
810 </div >
911 <div class =" rounded-lg inner-shadow py-2 px-2 lg:px-6 bg-base-950/40 dark:bg-base-900 [background-image:repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,255,255,0.01)_2px,rgba(255,255,255,0.01)_4px)]" >
10- <img defer src =" @Src" alt =" @Alt" loading =" lazy" style =" width : 100% ;" />
12+ @{
13+ var imgStyle = _aspectRatio != null
14+ ? $" width: 100%; aspect-ratio: {_aspectRatio };"
15+ : " width: 100%;" ;
16+ }
17+ <img fetchpriority =" high" src =" @Src" alt =" @Alt" style =" @imgStyle " />
1118 </div >
1219 </div >
1320</div >
1825
1926 [Parameter ]
2027 public string Alt { get ; set ; } = " Screenshot" ;
21- }
28+
29+ private string ? _aspectRatio ;
30+
31+ protected override void OnParametersSet ()
32+ {
33+ _aspectRatio = null ;
34+
35+ // Only process SVG files from /assets/
36+ if (! string .IsNullOrEmpty (Src ) &&
37+ Src .StartsWith (" /assets/" , StringComparison .OrdinalIgnoreCase ) &&
38+ Src .EndsWith (" .svg" , StringComparison .OrdinalIgnoreCase ))
39+ {
40+ _aspectRatio = TryGetAspectRatio (Src );
41+ }
42+ }
43+
44+ private string ? TryGetAspectRatio (string srcUrl )
45+ {
46+ try
47+ {
48+ // Map URL /assets/file.svg to Content/assets/file.svg
49+ var relativePath = srcUrl .TrimStart ('/' );
50+
51+ // Handle both Windows and Unix path separators
52+ if (Path .DirectorySeparatorChar != '/' )
53+ {
54+ relativePath = relativePath .Replace ('/' , Path .DirectorySeparatorChar );
55+ }
56+
57+ var contentPath = Path .Combine (Environment .ContentRootPath , " Content" , relativePath );
58+
59+ if (! File .Exists (contentPath ))
60+ {
61+ return null ;
62+ }
63+
64+ // Read only first 1KB - viewBox should be in opening <svg> tag
65+ using var stream = new FileStream (contentPath , FileMode .Open , FileAccess .Read , FileShare .Read );
66+ using var reader = new StreamReader (stream );
67+ var buffer = new char [1024 ];
68+ var charsRead = reader .Read (buffer , 0 , buffer .Length );
69+ var content = new string (buffer , 0 , charsRead );
70+
71+ // Parse viewBox="minX minY width height" - captures the last two numbers (width and height)
72+ var match = System .Text .RegularExpressions .Regex .Match (
73+ content ,
74+ @" viewBox\s*=\s*["" '][\d.\-]+\s+[\d.\-]+\s+([\d.]+)\s+([\d.]+)["" ']" );
75+
76+ if (match .Success &&
77+ double .TryParse (match .Groups [1 ].Value , System .Globalization .NumberStyles .Float ,
78+ System .Globalization .CultureInfo .InvariantCulture , out var width ) &&
79+ double .TryParse (match .Groups [2 ].Value , System .Globalization .NumberStyles .Float ,
80+ System .Globalization .CultureInfo .InvariantCulture , out var height ) &&
81+ height > 0 && width > 0 )
82+ {
83+ return FormattableString .Invariant ($" {width } / {height }" );
84+ }
85+ }
86+ catch
87+ {
88+ // Any error - fall back to no aspect ratio
89+ }
90+
91+ return null ;
92+ }
93+ }
0 commit comments