Browse Source

dom: started computed as string

mattias 11 months ago
parent
commit
1094f410fd
2 changed files with 387 additions and 52 deletions
  1. 369 52
      src/base/fresnel.dom.pas
  2. 18 0
      tests/base/TCFresnelCSS.pas

+ 369 - 52
src/base/fresnel.dom.pas

@@ -300,8 +300,10 @@ type
       TComputeEvent = procedure(Desc: TFresnelElementAttrDesc; El: TFresnelElement;
                                 var Value: string; // returns empty for invalid
                                 out Complete: boolean) of object;
+      TGetAsStringEvent = function(El: TFresnelElement): string of object;
   public
     OnCompute: TComputeEvent; // removes unknown/invalid values, substitutes calc(), converts to base unit (px)
+    OnAsString: TGetAsStringEvent; // returns value of shorthand (e.g. font or margin-block)
   end;
 
   { TFresnelCSSAttrDesc }
@@ -388,19 +390,40 @@ type
       TopLeft, TopRight, BottomLeft, BottomRight: TFresnelCSSAttribute; const Found: TCSSStringArray);
 
     // split shorthands
-    procedure SplitOverflow(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
-    procedure SplitBorderWidth(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+    procedure SplitBackground(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+    procedure SplitBackgroundPosition(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitBorderColor(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
-    procedure SplitBorderStyle(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitBorderLeftRightTopBottom(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual; // also fcaBorder
     procedure SplitBorderRadius(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+    procedure SplitBorderStyle(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+    procedure SplitBorderWidth(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitFont(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual; // todo systemfont
     procedure SplitMargin(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitMarginBlock(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitMarginInline(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+    procedure SplitOverflow(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
     procedure SplitPadding(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
-    procedure SplitBackgroundPosition(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
-    procedure SplitBackground(Resolver: TCSSBaseResolver; var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray); virtual;
+
+    // get computed attribute as string
+    function GetBackground(El: TFresnelElement): string; virtual;
+    function GetBorder(El: TFresnelElement): string; virtual;
+    function GetBorderColor(El: TFresnelElement): string; virtual;
+    function GetBorderSide(El: TFresnelElement; Side: TFresnelCSSSide): string; virtual;
+    function GetBorderLeft(El: TFresnelElement): string; virtual;
+    function GetBorderRight(El: TFresnelElement): string; virtual;
+    function GetBorderTop(El: TFresnelElement): string; virtual;
+    function GetBorderBottom(El: TFresnelElement): string; virtual;
+    function GetBorderStyle(El: TFresnelElement): string; virtual;
+    function GetBorderRadius(El: TFresnelElement): string; virtual;
+    function GetBorderWidth(El: TFresnelElement): string; virtual;
+    function GetSideLengths(El: TFresnelElement; LeftAttr: TFresnelCSSAttribute): string; virtual;
+    function GetFont(El: TFresnelElement): string; virtual;
+    function GetFontSize(El: TFresnelElement): string; virtual;
+    function GetMargin(El: TFresnelElement): string; virtual;
+    function GetMarginBlock(El: TFresnelElement): string; virtual;
+    function GetMarginInline(El: TFresnelElement): string; virtual;
+    function GetOverflow(El: TFresnelElement): string; virtual;
+    function GetPadding(El: TFresnelElement): string; virtual;
   public
     // keywords
     const
@@ -606,7 +629,9 @@ type
     // register attributes
     function AddFresnelAttr(Attr: TFresnelCSSAttribute; aInherits: boolean; const OnCheck: TCSSAttributeDesc.TCheckEvent): TFresnelCSSAttrDesc; virtual; overload;
     function AddFresnelShorthand(Attr: TFresnelCSSAttribute; const OnCheck: TCSSAttributeDesc.TCheckEvent;
-      const OnSplit: TCSSAttributeDesc.TSplitShorthandEvent; const CompProps: TFresnelCSSAttributeArray): TFresnelCSSAttrDesc; virtual; overload;
+      const OnSplit: TCSSAttributeDesc.TSplitShorthandEvent;
+      const OnAsString: TFresnelElementAttrDesc.TGetAsStringEvent;
+      const CompProps: TFresnelCSSAttributeArray): TFresnelCSSAttrDesc; virtual; overload;
     function AddFresnelLonghand(Attr: TFresnelCSSAttribute; Inherits: boolean; const OnCheck: TCSSAttributeDesc.TCheckEvent; const InitialValue: TCSSString = ''): TFresnelCSSAttrDesc; virtual; overload;
     // register pseudo classes
     function AddFresnelPseudoClass(Pseudo: TFresnelCSSPseudoClass): TFresnelCSSPseudoClassDesc; virtual; overload;
@@ -792,6 +817,8 @@ type
     function ConvertCSSValueToPixel(IsHorizontal: boolean; const Value: string): TFresnelLength; virtual;
     procedure ComputeDisplay; virtual;
     procedure ComputePosition; virtual;
+    function ComputeAttribute(aDesc: TCSSAttributeDesc; var aValue: String): TCSSAttributeValue.TState; virtual;
+    function GetShorthandSpaceSeparated(AttrDesc: TCSSAttributeDesc): string;
   protected
     procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
     procedure SetParentComponent(Value: TComponent); override;
@@ -854,12 +881,13 @@ type
     function GetComputedString(Attr: TFresnelCSSAttribute): string; virtual;
     function GetComputedCSSString(AttrID: TCSSNumericalID): string; virtual; overload;
     function GetComputedCSSString(const AttrName: string): string; overload;
-    function GetComputedCSSShorthand(AttrID: TCSSNumericalID): string; virtual;
+    function GetComputedCSSKeyword(AttrID: TCSSNumericalID; const AllowedKeywords: TCSSNumericalIDArray): TCSSNumericalID; virtual;
     function GetComputedBorderWidth(Attr: TFresnelCSSAttribute): TFresnelLength; virtual;
     function GetComputedBorderRadius(Corner: TFresnelCSSCorner): TFresnelPoint; virtual; // on fail returns 0
     function GetComputedColor(Attr: TFresnelCSSAttribute; const CurrentColor: TFPColor): TFPColor; virtual; // on fail returns transparent
     function GetComputedKeyword(Attr: TFresnelCSSAttribute; const AllowedKeywords: TCSSNumericalIDArray): TCSSNumericalID; virtual;
-    function GetComputedCSSKeyword(AttrID: TCSSNumericalID; const AllowedKeywords: TCSSNumericalIDArray): TCSSNumericalID; virtual;
+    procedure GetComputedMarginBlockStartEndAttr(out aStartAttr, aEndAttr: TFresnelCSSAttribute); virtual;
+    procedure GetComputedMarginInlineStartEndAttr(out aStartAttr, aEndAttr: TFresnelCSSAttribute); virtual;
     function GetComputedTextShadow(out aOffsetX, aOffsetY, aRadius: TFresnelLength; out aColor: TFPColor): boolean; virtual; // on fail returns 0
     function GetComputedImage(Attr: TFresnelCSSAttribute): TFresnelCSSBackgroundInfo; virtual; // on fail returns nil
     function GetComputedLinearGradient(const LGParams: string): TFresnelCSSLinearGradient; virtual; // on fail returns nil
@@ -2520,6 +2548,218 @@ begin
     fcaPaddingTop,fcaPaddingRight,fcaPaddingBottom,fcaPaddingLeft,Found);
 end;
 
+function TFresnelCSSRegistry.GetBackground(El: TFresnelElement): string;
+begin
+  Result:='';
+  // todo
+  if El=nil then ;
+end;
+
+function TFresnelCSSRegistry.GetBorder(El: TFresnelElement): string;
+var
+  aLeft, aRight, aTop, aBottom: String;
+begin
+  aLeft:=GetBorderSide(El,ffsLeft);
+  aRight:=GetBorderSide(El,ffsRight);
+  aTop:=GetBorderSide(El,ffsTop);
+  aBottom:=GetBorderSide(El,ffsBottom);
+
+  if (aLeft=aRight) and (aLeft=aTop) and (aLeft=aBottom) then
+    Result:=aLeft
+  else
+    Result:='';
+end;
+
+function TFresnelCSSRegistry.GetBorderColor(El: TFresnelElement): string;
+var
+  aLeft, aRight, aTop, aBottom: String;
+begin
+  aLeft:=El.GetComputedString(fcaBorderLeftColor);
+  aRight:=El.GetComputedString(fcaBorderRightColor);
+  aTop:=El.GetComputedString(fcaBorderTopColor);
+  aBottom:=El.GetComputedString(fcaBorderBottomColor);
+
+  if aLeft='' then aLeft:='currentcolor';
+  if aRight='' then aRight:='currentcolor';
+  if aTop='' then aTop:='currentcolor';
+  if aBottom='' then aBottom:='currentcolor';
+
+  Result:=aTop+' '+aRight+' '+aBottom+' '+aLeft;
+end;
+
+function TFresnelCSSRegistry.GetBorderSide(El: TFresnelElement; Side: TFresnelCSSSide): string;
+var
+  aColor, aStyle, aWidth: String;
+begin
+  aColor:=El.GetComputedString(TFresnelCSSAttribute(ord(fcaBorderLeftColor)+ord(Side)));
+  if aColor='' then aColor:='currentcolor';
+  aStyle:=El.GetComputedString(TFresnelCSSAttribute(ord(fcaBorderLeftStyle)+ord(Side)));
+  if aStyle='' then aStyle:='none';
+  aWidth:=El.GetComputedString(TFresnelCSSAttribute(ord(fcaBorderLeftWidth)+ord(Side)));
+  if aWidth='' then aWidth:='0';
+
+  Result:=aColor+' '+aStyle+' '+aWidth;
+end;
+
+function TFresnelCSSRegistry.GetBorderLeft(El: TFresnelElement): string;
+begin
+  Result:=GetBorderSide(El,ffsLeft);
+end;
+
+function TFresnelCSSRegistry.GetBorderRight(El: TFresnelElement): string;
+begin
+  Result:=GetBorderSide(El,ffsRight);
+end;
+
+function TFresnelCSSRegistry.GetBorderTop(El: TFresnelElement): string;
+begin
+  Result:=GetBorderSide(El,ffsTop);
+end;
+
+function TFresnelCSSRegistry.GetBorderBottom(El: TFresnelElement): string;
+begin
+  Result:=GetBorderSide(El,ffsBottom);
+end;
+
+function TFresnelCSSRegistry.GetBorderWidth(El: TFresnelElement): string;
+begin
+  Result:=GetSideLengths(El,fcaBorderLeftWidth);
+end;
+
+function TFresnelCSSRegistry.GetSideLengths(El: TFresnelElement; LeftAttr: TFresnelCSSAttribute
+  ): string;
+var
+  Sides: array[TFresnelCSSSide] of TFresnelLength;
+  Side: TFresnelCSSSide;
+begin
+  for Side in TFresnelCSSSide do
+    Sides[Side]:=El.GetComputedLength(TFresnelCSSAttribute(ord(LeftAttr)+ord(Side)));
+
+  Result:=FloatToCSSPx(Sides[ffsTop])
+     +' '+FloatToCSSPx(Sides[ffsRight])
+     +' '+FloatToCSSPx(Sides[ffsBottom])
+     +' '+FloatToCSSPx(Sides[ffsLeft]);
+end;
+
+function TFresnelCSSRegistry.GetFont(El: TFresnelElement): string;
+
+  procedure Add(const aValue, aDefault: string);
+  begin
+    if aValue='' then exit;
+    if aValue=aDefault then exit;
+
+    if Result>'' then
+      Result+=' '+aValue
+    else
+      Result:=aValue;
+  end;
+
+var
+  aSize, aWeight, aLineHeight: TFresnelLength;
+  aFamily, aStyle, aVariant: String;
+begin
+  Result:='';
+
+  aSize:=El.Font.GetSize;
+  aFamily:=El.Font.GetFamily;
+  aStyle:=El.Font.GetStyle;
+
+  aVariant:=El.Font.GetVariant;
+  case aVariant of
+  'normal','small-caps': ; // only these are allowed in font shorthand
+  else aVariant:='';
+  end;
+
+  aWeight:=El.Font.GetWeight;
+  // todo font-width, must be a single keyword
+  aLineHeight:=El.GetComputedLength(fcaLineHeight);
+
+  // style, variant and weight must preced size
+  Add(aStyle,FresnelAttrs[fcaFontStyle].InitialValue);
+  Add(aVariant,FresnelAttrs[fcaFontVariant].InitialValue);
+  Add(FloatToCSSStr(aWeight),'');
+
+  // font-size/line-height
+  Add(FloatToCSSStr(aSize),'');
+  Result+='/'+FloatToCSSStr(aLineHeight);
+
+  Add(aFamily,'');
+end;
+
+function TFresnelCSSRegistry.GetFontSize(El: TFresnelElement): string;
+begin
+  Result:=FloatToCSSPx(El.GetComputedFontSize);
+end;
+
+function TFresnelCSSRegistry.GetMargin(El: TFresnelElement): string;
+var
+  Margins: array[TFresnelCSSSide] of TFresnelLength;
+  Side: TFresnelCSSSide;
+begin
+  for Side in TFresnelCSSSide do
+    Margins[Side]:=El.GetComputedLength(TFresnelCSSAttribute(ord(fcaMarginLeft)+ord(Side)));
+
+  Result:=FloatToCSSPx(Margins[ffsTop])+' '+FloatToCSSPx(Margins[ffsRight])
+     +' '+FloatToCSSPx(Margins[ffsBottom])+' '+FloatToCSSPx(Margins[ffsLeft]);
+end;
+
+function TFresnelCSSRegistry.GetMarginBlock(El: TFresnelElement): string;
+var
+  aStartAttr, aEndAttr: TFresnelCSSAttribute;
+  aStart, aEnd: TFresnelLength;
+begin
+  El.GetComputedMarginBlockStartEndAttr(aStartAttr,aEndAttr);
+  aStart:=El.GetComputedLength(aStartAttr);
+  aEnd:=El.GetComputedLength(aEndAttr);
+  Result:=FloatToCSSPx(aStart)+' '+FloatToCSSPx(aEnd);
+end;
+
+function TFresnelCSSRegistry.GetMarginInline(El: TFresnelElement): string;
+var
+  aStartAttr, aEndAttr: TFresnelCSSAttribute;
+  aStart, aEnd: TFresnelLength;
+begin
+  El.GetComputedMarginInlineStartEndAttr(aStartAttr,aEndAttr);
+  aStart:=El.GetComputedLength(aStartAttr);
+  aEnd:=El.GetComputedLength(aEndAttr);
+  Result:=FloatToCSSPx(aStart)+' '+FloatToCSSPx(aEnd);
+end;
+
+function TFresnelCSSRegistry.GetBorderStyle(El: TFresnelElement): string;
+var
+  aLeft, aRight, aTop, aBottom: String;
+begin
+  aLeft:=El.GetComputedString(fcaBorderLeftStyle);
+  aRight:=El.GetComputedString(fcaBorderRightStyle);
+  aTop:=El.GetComputedString(fcaBorderTopStyle);
+  aBottom:=El.GetComputedString(fcaBorderBottomStyle);
+
+  if aLeft='' then aLeft:='none';
+  if aRight='' then aRight:='none';
+  if aTop='' then aTop:='none';
+  if aBottom='' then aBottom:='none';
+
+  Result:=aTop+' '+aRight+' '+aBottom+' '+aLeft;
+end;
+
+function TFresnelCSSRegistry.GetBorderRadius(El: TFresnelElement): string;
+var
+  aCorner: TFresnelCSSCorner;
+  Radii: array[TFresnelCSSCorner] of TFresnelPoint;
+begin
+  for aCorner in TFresnelCSSCorner do
+    Radii[aCorner]:=El.GetComputedBorderRadius(aCorner);
+
+  Result:=FloatToCSSPx(Radii[fcsTopLeft].X)
+     +' '+FloatToCSSPx(Radii[fcsTopRight].X)
+     +' '+FloatToCSSPx(Radii[fcsBottomRight].X)
+     +' '+FloatToCSSPx(Radii[fcsBottomLeft].X)
+     +' / '+FloatToCSSPx(Radii[fcsTopLeft].Y)
+     +' '+FloatToCSSPx(Radii[fcsTopRight].Y)
+     +' '+FloatToCSSPx(Radii[fcsBottomRight].Y)
+     +' '+FloatToCSSPx(Radii[fcsBottomLeft].Y);
+end;
+
 procedure TFresnelCSSRegistry.SplitBackgroundPosition(Resolver: TCSSBaseResolver;
   var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray);
 var
@@ -2568,6 +2808,26 @@ begin
   Values[8]:=aSize;
 end;
 
+function TFresnelCSSRegistry.GetOverflow(El: TFresnelElement): string;
+var
+  X, Y: String;
+begin
+  X:=El.GetComputedString(fcaOverflowX);
+  Y:=El.GetComputedString(fcaOverflowY);
+  if X>'' then
+  begin
+    Result:=X;
+    if (Y>'') and (X<>Y) then
+      Result+=' '+Y;
+  end else
+    Result:=Y;
+end;
+
+function TFresnelCSSRegistry.GetPadding(El: TFresnelElement): string;
+begin
+  Result:=GetSideLengths(El,fcaPaddingLeft);
+end;
+
 constructor TFresnelCSSRegistry.Create;
 begin
   Attribute_ClassOf:=TFresnelCSSAttrDesc;
@@ -2775,7 +3035,7 @@ begin
   AddFresnelLonghand(fcaOverflowY,false,@CheckOverflowXY,'visible');
   Chk_OverflowXY_KeywordIDs:=[kwVisible,kwHidden,kwClip,kwScroll,kwAuto];
 
-  AddFresnelShorthand(fcaOverflow,@CheckOverflowXY,@SplitOverflow,[fcaOverflowX,fcaOverflowY]);
+  AddFresnelShorthand(fcaOverflow,@CheckOverflowXY,@SplitOverflow,@GetOverflow,[fcaOverflowX,fcaOverflowY]);
 
   // clear
   AddFresnelLonghand(fcaClear,false,@CheckClear);
@@ -2817,7 +3077,7 @@ begin
   Chk_BorderWidth_Dim.AllowFrac:=true;
   Chk_BorderWidth_Dim.AllowedKeywordIDs:=[kwThin,kwMedium,kwThick];
 
-  AddFresnelShorthand(fcaBorderWidth,@CheckBorderWidth,@SplitBorderWidth,
+  AddFresnelShorthand(fcaBorderWidth,@CheckBorderWidth,@SplitBorderWidth,@GetBorderWidth,
     [fcaBorderLeftWidth,fcaBorderRightWidth,fcaBorderTopWidth,fcaBorderBottomWidth]);
 
   // border-color
@@ -2826,7 +3086,7 @@ begin
   AddFresnelLonghand(fcaBorderTopColor,false,@CheckColor,'currentcolor');
   AddFresnelLonghand(fcaBorderBottomColor,false,@CheckColor,'currentcolor');
 
-  AddFresnelShorthand(fcaBorderColor,@CheckColor,@SplitBorderColor,
+  AddFresnelShorthand(fcaBorderColor,@CheckColor,@SplitBorderColor,@GetBorderColor,
     [fcaBorderLeftColor,fcaBorderRightColor,fcaBorderTopColor,fcaBorderBottomColor]);
 
   // border-style
@@ -2836,21 +3096,21 @@ begin
   AddFresnelLonghand(fcaBorderBottomStyle,false,@CheckBorderStyle);
   Chk_BorderStyle_KeywordIDs:=[kwNone,kwHidden,kwDotted,kwDashed,kwSolid,kwDouble,kwGroove,kwRidge,kwInset,kwOutset];
 
-  AddFresnelShorthand(fcaBorderStyle,@CheckBorderStyle,@SplitBorderStyle,
+  AddFresnelShorthand(fcaBorderStyle,@CheckBorderStyle,@SplitBorderStyle,@GetBorderStyle,
     [fcaBorderLeftStyle,fcaBorderRightStyle,fcaBorderTopStyle,fcaBorderBottomStyle]);
 
   // border-left, -right, -top, -bottom
-  AddFresnelShorthand(fcaBorderLeft,@CheckBorder,@SplitBorderLeftRightTopBottom,
+  AddFresnelShorthand(fcaBorderLeft,@CheckBorder,@SplitBorderLeftRightTopBottom,@GetBorderLeft,
     [fcaBorderLeftWidth,fcaBorderLeftStyle,fcaBorderLeftColor]);
-  AddFresnelShorthand(fcaBorderRight,@CheckBorder,@SplitBorderLeftRightTopBottom,
+  AddFresnelShorthand(fcaBorderRight,@CheckBorder,@SplitBorderLeftRightTopBottom,@GetBorderRight,
     [fcaBorderRightWidth,fcaBorderRightStyle,fcaBorderRightColor]);
-  AddFresnelShorthand(fcaBorderTop,@CheckBorder,@SplitBorderLeftRightTopBottom,
+  AddFresnelShorthand(fcaBorderTop,@CheckBorder,@SplitBorderLeftRightTopBottom,@GetBorderTop,
     [fcaBorderTopWidth,fcaBorderTopStyle,fcaBorderTopColor]);
-  AddFresnelShorthand(fcaBorderBottom,@CheckBorder,@SplitBorderLeftRightTopBottom,
+  AddFresnelShorthand(fcaBorderBottom,@CheckBorder,@SplitBorderLeftRightTopBottom,@GetBorderBottom,
     [fcaBorderBottomWidth,fcaBorderBottomStyle,fcaBorderBottomColor]);
 
   // border: shorthand for all 12 border-[left,right,top,bottom]-[widht,style,color]
-  AddFresnelShorthand(fcaBorder,@CheckBorder,@SplitBorderLeftRightTopBottom,
+  AddFresnelShorthand(fcaBorder,@CheckBorder,@SplitBorderLeftRightTopBottom,@GetBorder,
     [fcaBorderWidth,fcaBorderStyle,fcaBorderColor, // overrides these shorthands too
      fcaBorderLeftWidth,fcaBorderLeftStyle,fcaBorderLeftColor,
      fcaBorderRightWidth,fcaBorderRightStyle,fcaBorderRightColor,
@@ -2864,7 +3124,7 @@ begin
   AddFresnelLonghand(fcaBorderBottomRightRadius,false,@CheckBorderRadius);
   Chk_BorderRadius_Dim.AllowedUnits:=cuAllLengthsAndPercent;
   Chk_BorderRadius_Dim.AllowFrac:=true;
-  AddFresnelShorthand(fcaBorderRadius,@CheckBorderRadius,@SplitBorderRadius,
+  AddFresnelShorthand(fcaBorderRadius,@CheckBorderRadius,@SplitBorderRadius,@GetBorderRadius,
     [fcaBorderTopLeftRadius,fcaBorderTopRightRadius,fcaBorderBottomLeftRadius,fcaBorderBottomRightRadius]);
 
   // float
@@ -2880,7 +3140,8 @@ begin
   AddFresnelLonghand(fcaFontKerning,true,@CheckFontKerning,'auto');
   Chk_FontKerning_KeywordIDs:=[kwAuto,kwNormal,kwNone];
   // font-size
-  AddFresnelLonghand(fcaFontSize,true,@CheckFontSize,'medium');
+  Desc:=AddFresnelLonghand(fcaFontSize,true,@CheckFontSize,'medium');
+  Desc.OnAsString:=@GetFontSize;
   Chk_FontSize_Dim.AllowedUnits:=cuAllLengthsAndPercent;
   Chk_FontSize_Dim.AllowFrac:=true;
   Chk_FontSize_Dim.AllowedKeywordIDs:=[kwXXSmall,kwXSmall,kwSmall,kwSmaller,kwMedium,
@@ -2908,7 +3169,7 @@ begin
   Chk_LineHeight_Dim.AllowFrac:=true;
 
   // font
-  AddFresnelShorthand(fcaFont,@CheckFont,@SplitFont,
+  AddFresnelShorthand(fcaFont,@CheckFont,@SplitFont,@GetFont,
     [fcaFontFamily,fcaFontSize,fcaFontStyle,fcaFontVariant,fcaFontWeight,fcaLineHeight]);
 
   // text-shadow
@@ -2928,19 +3189,19 @@ begin
   Chk_Margin_Dim.AllowedUnits:=cuAllLengthsAndPercent;
   Chk_Margin_Dim.AllowFrac:=true;
   Chk_Margin_Dim.AllowNegative:=true;
-  AddFresnelShorthand(fcaMargin,@CheckMargin,@SplitMargin,
+  AddFresnelShorthand(fcaMargin,@CheckMargin,@SplitMargin,@GetMargin,
     [fcaMarginLeft,fcaMarginRight,fcaMarginTop,fcaMarginBottom]);
 
   // margin-block
   AddFresnelLonghand(fcaMarginBlockEnd,false,@CheckMargin,'0');
   AddFresnelLonghand(fcaMarginBlockStart,false,@CheckMargin,'0');
-  AddFresnelShorthand(fcaMarginBlock,@CheckMargin,@SplitMarginBlock,
+  AddFresnelShorthand(fcaMarginBlock,@CheckMargin,@SplitMarginBlock,@GetMarginBlock,
     [fcaMarginBlockStart,fcaMarginBlockEnd]);
 
   // margin-inline
   AddFresnelLonghand(fcaMarginInlineEnd,false,@CheckMargin,'0');
   AddFresnelLonghand(fcaMarginInlineStart,false,@CheckMargin,'0');
-  AddFresnelShorthand(fcaMarginInline,@CheckMargin,@SplitMarginInline,
+  AddFresnelShorthand(fcaMarginInline,@CheckMargin,@SplitMarginInline,@GetMarginInline,
     [fcaMarginInlineStart,fcaMarginInlineEnd]);
 
   // opacity
@@ -2955,7 +3216,7 @@ begin
   AddFresnelLonghand(fcaPaddingBottom,false,@CheckPadding,'0');
   Chk_Padding_Dim.AllowedUnits:=cuAllLengthsAndPercent;
   Chk_Padding_Dim.AllowFrac:=true;
-  AddFresnelShorthand(fcaPadding,@CheckPadding,@SplitPadding,
+  AddFresnelShorthand(fcaPadding,@CheckPadding,@SplitPadding,@GetPadding,
     [fcaPaddingLeft,fcaPaddingRight,fcaPaddingTop,fcaPaddingBottom]);
 
   // opacity
@@ -3000,7 +3261,7 @@ begin
   Chk_BackgroundSize_Dim.AllowFrac:=true;
 
   // background
-  AddFresnelShorthand(fcaBackground,@CheckBackground,@SplitBackground,
+  AddFresnelShorthand(fcaBackground,@CheckBackground,@SplitBackground,@GetBackground,
     [fcaBackgroundAttachment,fcaBackgroundClip,fcaBackgroundColor,fcaBackgroundImage,
      fcaBackgroundOrigin,fcaBackgroundRepeat,fcaBackgroundSize,fcaBackgroundPosition,
      fcaBackgroundPositionX,fcaBackgroundPositionY]);
@@ -3033,7 +3294,8 @@ end;
 
 function TFresnelCSSRegistry.AddFresnelShorthand(Attr: TFresnelCSSAttribute;
   const OnCheck: TCSSAttributeDesc.TCheckEvent; const OnSplit: TCSSAttributeDesc.
-  TSplitShorthandEvent; const CompProps: TFresnelCSSAttributeArray): TFresnelCSSAttrDesc;
+  TSplitShorthandEvent; const OnAsString: TFresnelElementAttrDesc.TGetAsStringEvent;
+  const CompProps: TFresnelCSSAttributeArray): TFresnelCSSAttrDesc;
 var
   i: Integer;
   AttrDesc: TFresnelCSSAttrDesc;
@@ -3042,6 +3304,7 @@ begin
     raise EFresnel.Create('20240712135716');
   Result:=AddFresnelAttr(Attr,false,OnCheck);
   Result.OnSplitShorthand:=OnSplit;
+  Result.OnAsString:=OnAsString;
   SetLength(Result.CompProps,length(CompProps));
   for i:=0 to high(CompProps) do
   begin
@@ -4175,37 +4438,65 @@ function TFresnelElement.GetCSSString(AttrID: TCSSNumericalID; Compute: boolean;
 var
   AttrDesc: TCSSAttributeDesc;
 
-  procedure DoCompute;
+  procedure UseInitialValue;
+  var
+    aState: TCSSAttributeValue.TState;
   begin
-    if not (AttrDesc is TFresnelCSSAttrDesc) then exit;
+    Complete:=false;
+    Result:=AttrDesc.InitialValue;
+    if Compute then
+    begin
+      aState:=ComputeAttribute(AttrDesc,Result);
+      Complete:=aState=cavsComputed;
+    end;
   end;
 
 var
   i: Integer;
   aValue: TCSSAttributeValue;
+  ElAttrDesc: TFresnelElementAttrDesc;
+  aState: TCSSAttributeValue.TState;
 begin
   Complete:=true;
 
   AttrDesc:=Resolver.GetAttributeDesc(AttrID);
   if AttrDesc=nil then
     exit('');
-  if length(AttrDesc.CompProps)>0 then
+
+  if Compute and (AttrDesc is TFresnelElementAttrDesc) then
   begin
-    Result:=GetComputedCSSShorthand(AttrID);
-    exit;
+    ElAttrDesc:=TFresnelElementAttrDesc(AttrDesc);
+    if Assigned(ElAttrDesc.OnAsString) then
+    begin
+      // shorthand
+      Result:=ElAttrDesc.OnAsString(Self);
+      exit;
+    end;
   end;
 
   if FCSSValues<>nil then
   begin
     i:=FCSSValues.IndexOf(AttrID);
-    aValue:=FCSSValues.Values[i];
-    if (i>=0) and (aValue.State<>cavsInvalid) then
+    if (i>=0) then
     begin
-      Complete:=aValue.State=cavsComputed;
-      Result:=aValue.Value;
-      if Compute then
-        DoCompute;
-      exit;
+      aValue:=FCSSValues.Values[i];
+      aState:=aValue.State;
+      if aState<>cavsInvalid then
+      begin
+        // element has value
+        Complete:=aState=cavsComputed;
+        if Compute and not Complete then
+        begin
+          aState:=ComputeAttribute(AttrDesc,FCSSValues.Values[i].Value);
+          aValue.State:=aState;
+          Complete:=aState=cavsComputed;
+        end;
+        if aState<>cavsInvalid then
+        begin
+          Result:=aValue.Value;
+          if Result>'' then exit;
+        end;
+      end;
     end;
 
     if AttrDesc.All then
@@ -4219,10 +4510,7 @@ begin
         end;
       CSSKeywordInitial:
         begin
-          Complete:=false;
-          Result:=AttrDesc.InitialValue;
-          if Compute then
-            DoCompute;
+          UseInitialValue;
           exit;
         end;
       CSSKeywordUnset,
@@ -4235,12 +4523,8 @@ begin
   // use default
   if AttrDesc.Inherits and (Parent<>nil) then
     Result:=Parent.GetCSSString(AttrID,true,Complete)
-  else begin
-    Complete:=false;
-    Result:=AttrDesc.InitialValue;
-    if Compute then
-      DoCompute;
-  end;
+  else
+    UseInitialValue;
 end;
 
 function TFresnelElement.GetComputedLength(Attr: TFresnelCSSAttribute; UseNaNOnFail: boolean
@@ -4350,14 +4634,13 @@ begin
   Result:=GetComputedCSSString(AttrID);
 end;
 
-function TFresnelElement.GetComputedCSSShorthand(AttrID: TCSSNumericalID): string;
+function TFresnelElement.GetShorthandSpaceSeparated(AttrDesc: TCSSAttributeDesc): string;
 var
-  AttrDesc, SubDesc: TCSSAttributeDesc;
+  SubDesc: TCSSAttributeDesc;
   i: Integer;
   s: String;
 begin
   Result:='';
-  AttrDesc:=Resolver.GetAttributeDesc(AttrID);
   if AttrDesc=nil then exit;
   for i:=0 to length(AttrDesc.CompProps)-1 do
   begin
@@ -4654,6 +4937,20 @@ begin
   Result:=GetComputedCSSKeyword(CSSRegistry.FresnelAttrs[Attr].Index,AllowedKeywords);
 end;
 
+procedure TFresnelElement.GetComputedMarginBlockStartEndAttr(out aStartAttr,
+  aEndAttr: TFresnelCSSAttribute);
+begin
+  aStartAttr:=fcaMarginLeft;
+  aEndAttr:=fcaMarginRight;
+end;
+
+procedure TFresnelElement.GetComputedMarginInlineStartEndAttr(out aStartAttr,
+  aEndAttr: TFresnelCSSAttribute);
+begin
+  aStartAttr:=fcaMarginLeft;
+  aEndAttr:=fcaMarginRight;
+end;
+
 function TFresnelElement.GetComputedCSSKeyword(AttrID: TCSSNumericalID;
   const AllowedKeywords: TCSSNumericalIDArray): TCSSNumericalID;
 var
@@ -4968,6 +5265,7 @@ function TFresnelElement.GetFont: IFresnelFont;
 var
   s: String;
   aComp: TCSSResCompValue;
+  Complete: boolean;
 begin
   if fesFontDescValid in FStates then
     exit(FFont);
@@ -4980,7 +5278,7 @@ begin
   FFontDesc.Kerning:=GetComputedString(fcaFontKerning);
 
   FFontDesc.Size:=FresnelDefaultFontSize;
-  s:=GetComputedString(fcaFontSize);
+  s:=GetCSSString(CSSRegistry.FresnelAttrs[fcaFontSize].Index,false,Complete);
   if Parent<>nil then
   begin
     aComp.EndP:=PChar(s);
@@ -5307,6 +5605,25 @@ begin
   FPosition:=GetComputedKeyword(fcaPosition,CSSRegistry.Chk_Position_KeywordIDs);
 end;
 
+function TFresnelElement.ComputeAttribute(aDesc: TCSSAttributeDesc; var aValue: String
+  ): TCSSAttributeValue.TState;
+var
+  ElAttrDesc: TFresnelElementAttrDesc;
+begin
+  if aValue='' then exit(cavsInvalid);
+
+  Result:=cavsComputed;
+
+  if aDesc is TFresnelElementAttrDesc then
+  begin
+    ElAttrDesc:=TFresnelElementAttrDesc(aDesc);
+    if ElAttrDesc.OnCompute<>nil then
+    begin
+
+    end;
+  end;
+end;
+
 class function TFresnelElement.GetCSSTypeStyle: TCSSString;
 begin
   Result:='';

+ 18 - 0
tests/base/TCFresnelCSS.pas

@@ -123,6 +123,7 @@ type
     procedure TestSetStyleAttr_ReplaceMiddleValue;
 
     procedure Test_FontSize_Percentage;
+    procedure Test_FontSize_AsString;
 
     procedure TestVar_NoDefault;
     procedure TestVar_Initial;
@@ -722,6 +723,23 @@ begin
   AssertEquals('Div1.GetComputedFontSize',Div1.GetComputedFontSize,60);
 end;
 
+procedure TTestFresnelCSS.Test_FontSize_AsString;
+var
+  Body: TBody;
+begin
+  Viewport.Stylesheet.Text:=LinesToStr([
+    'body {',
+    'font-size:3em;',
+    '}']);
+  Body:=TBody.Create(Viewport);
+  Body.Name:='Body';
+  Body.Parent:=Viewport;
+
+  Viewport.ApplyCSS;
+  AssertEquals('Body.GetComputedFontSize',30,Body.GetComputedFontSize);
+  AssertEquals('Body.GetComputedFontSize','30px',Body.GetComputedString(fcaFontSize));
+end;
+
 procedure TTestFresnelCSS.TestVar_NoDefault;
 var
   Body: TBody;