Editor/Document/Length.cs
using System;
using System.Globalization;
namespace Grains.RazorDesigner.Document;
// No fr unit: sbox is flexbox not CSS Grid. Use FlexGrow for fractional sizing.
public readonly record struct Length( float Value, LengthUnit Unit )
{
public static Length Auto => new( 0f, LengthUnit.Auto );
public static Length Px( float v ) => new( v, LengthUnit.Px );
public static Length Percent( float v ) => new( v, LengthUnit.Percent );
public static Length Rem( float v ) => new( v, LengthUnit.Rem );
public static Length Em( float v ) => new( v, LengthUnit.Em );
public string ToCss() => Unit switch
{
LengthUnit.Auto => "auto",
LengthUnit.Px => $"{Value:0.##}px",
LengthUnit.Percent => $"{Value:0.##}%",
LengthUnit.Rem => $"{Value:0.##}rem",
LengthUnit.Em => $"{Value:0.##}em",
_ => "auto",
};
public override string ToString() => ToCss();
public static Length Parse( string s )
{
if ( !TryParse( s, out var v ) )
throw new FormatException( $"Length.Parse: cannot parse \"{s}\"" );
return v;
}
public static bool TryParse( string s, out Length result )
{
result = Auto;
if ( string.IsNullOrWhiteSpace( s ) ) return false;
var t = s.Trim();
if ( t.Equals( "auto", StringComparison.OrdinalIgnoreCase ) )
{
result = Auto;
return true;
}
// Strip suffix; whatever's left must parse as a float.
string suffix = "";
int splitAt = t.Length;
for ( int i = 0; i < t.Length; i++ )
{
var c = t[i];
if ( char.IsDigit( c ) || c == '.' || c == '-' || c == '+' ) continue;
splitAt = i;
break;
}
var numPart = t.Substring( 0, splitAt );
suffix = t.Substring( splitAt ).Trim().ToLowerInvariant();
if ( !float.TryParse( numPart, NumberStyles.Float, CultureInfo.InvariantCulture, out var num ) )
return false;
result = suffix switch
{
"" => Px( num ), // bare number -> px (forgiving)
"px" => Px( num ),
"%" => Percent( num ),
"rem" => Rem( num ),
"em" => Em( num ),
_ => default,
};
return suffix is "" or "px" or "%" or "rem" or "em";
}
}
public enum LengthUnit { Px, Percent, Auto, Rem, Em }