Browse Source

fcl-css: resolver: added colors, replaced filename with name, registry stamp

mattias 11 months ago
parent
commit
c8eeb2ec54

+ 23 - 2
packages/fcl-css/src/fpcssparser.pp

@@ -132,6 +132,7 @@ Type
 
 Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
 Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
+Function IsValidCSSAttributeName(const aName: TCSSString): boolean;
 
 implementation
 
@@ -190,6 +191,26 @@ begin
   end;
 end;
 
+function IsValidCSSAttributeName(const aName: TCSSString): boolean;
+var
+  p, StartP: PCSSChar;
+begin
+  if aName='' then exit(false);
+  StartP:=PCSSChar(aName);
+  p:=StartP;
+  if p^='-' then
+  begin
+    inc(p);
+    if p^='-' then
+      inc(p);
+    if not (p^ in ['A'..'Z','a'..'z']) then
+      exit;
+    inc(p);
+  end;
+  while p^ in ['A'..'Z','a'..'z','_','-'] do inc(p);
+  Result:=p=StartP+length(aName);
+end;
+
 { TCSSParser }
 
 function TCSSParser.GetAtEOF: Boolean;
@@ -737,13 +758,13 @@ begin
   case CurrentToken of
   ctkPERCENTAGE:
     begin
-    Result:=cuPERCENT;
+    Result:=cuPercent;
     Consume(CurrentToken);
     end;
   ctkIDENTIFIER:
     begin
     p:=PCSSChar(CurrentTokenString);
-    for U:=Succ(cuNONE) to High(TCSSUnit) do
+    for U:=Succ(cuNone) to High(TCSSUnit) do
       if CompareMem(p,PCSSChar(CSSUnitNames[U]),SizeOf(TCSSChar)*length(CSSUnitNames[U])) then
         begin
         Result:=U;

+ 248 - 69
packages/fcl-css/src/fpcssresolver.pas

@@ -208,10 +208,17 @@ type
   { TCSSAttributeValue }
 
   TCSSAttributeValue = class
+  public
+    type
+      TState = (
+        cavsSource, // value from CSS - simple normalization, e.g. no comments, some spaces differ, floats
+        cavsBaseKeywords, // base keywords resolved e.g. "initial" or "inherit"
+        cavsComputed, // has final result
+        cavsInvalid // skip this value
+        );
   public
     AttrID: TCSSNumericalID; // the resolver has substituted all shorthands
-    Computed: boolean;
-    Invalid: boolean;
+    State: TState;
     Value: TCSSString; // the resolver has substituted all var() calls
   end;
   TCSSAttributeValueArray = array of TCSSAttributeValue;
@@ -220,12 +227,12 @@ type
 
   TCSSAttributeValues = class
   public
-    AllValue: TCSSString;
+    AllValue: TCSSNumericalID;
     Values: TCSSAttributeValueArray; // the resolver sorts them ascending for AttrID, shorthands are already replaced with longhands
     procedure SortValues; virtual; // ascending AttrID
     procedure SwapValues(Index1, Index2: integer);
     function IndexOf(AttrID: TCSSNumericalID): integer;
-    function GetValue(AttrDesc: TCSSAttributeDesc): TCSSString;
+    procedure SetComputedValue(AttrID: TCSSNumericalID; const aValue: TCSSString);
     destructor Destroy; override;
   end;
 
@@ -290,7 +297,7 @@ type
     type
       TStyleSheet = class
         Source: TCSSString;
-        Filename: TCSSString;
+        Name: TCSSString; // case sensitive
         Origin: TCSSOrigin;
         Element: TCSSElement;
         Parsed: boolean;
@@ -352,6 +359,7 @@ type
     // parse stylesheets
     procedure ParseSource(Index: integer); virtual;
     function ParseCSSSource(const Src: TCSSString; Inline: boolean): TCSSElement; virtual;
+    procedure ClearElements; virtual;
 
     // resolving rules
     procedure ComputeElement(El: TCSSElement); virtual;
@@ -391,7 +399,7 @@ type
     function CreateSharedRuleList: TCSSSharedRuleList; virtual; // using FElRules, sets FMergedAttributes
 
     // merge properties
-    procedure InitMerge; virtual;
+    procedure ClearMerge; virtual;
     procedure SetMergedAttribute(AttrID, aSpecifity: TCSSNumericalID; DeclEl: TCSSDeclarationElement);
     procedure RemoveMergedAttribute(AttrID: TCSSNumericalID);
     procedure MergeAttribute(El: TCSSElement; aSpecifity: TCSSSpecifity); virtual;
@@ -411,21 +419,24 @@ type
     procedure Init; virtual; // call after adding stylesheets and before computing all nodes
     function GetElPath(El: TCSSElement): TCSSString; virtual;
     function GetElPos(El: TCSSElement): TCSSString; virtual;
-    function ParseInlineStyle(const Src: TCSSString): TCSSElement; virtual;
+    function ParseInlineStyle(const Src: TCSSString): TCSSRuleElement; virtual;
     procedure Compute(Node: ICSSNode;
       InlineStyle: TCSSRuleElement; // inline style of Node
       out Rules: TCSSSharedRuleList {owned by resolver};
       out Values: TCSSAttributeValues
       ); virtual;
     function GetAttributeDesc(AttrId: TCSSNumericalID): TCSSAttributeDesc; virtual;
+    function GetDeclarationValue(Decl: TCSSDeclarationElement): TCSSString; virtual;
   public
     property Options: TCSSResolverOptions read FOptions write SetOptions;
     property StringComparison: TCSSResStringComparison read FStringComparison;
   public
     // stylesheets
     procedure ClearStyleSheets; virtual;
-    function AddStyleSheet(const aSource, aFilename: TCSSString; anOrigin: TCSSOrigin): TStyleSheet; virtual;
+    function AddStyleSheet(anOrigin: TCSSOrigin; const aName: TCSSString; const aSource: TCSSString): TStyleSheet; virtual;
+    procedure ReplaceStyleSheet(Index: integer; const NewSource: TCSSString); virtual;
     function IndexOfStyleSheetWithElement(El: TCSSElement): integer;
+    function IndexOfStyleSheetWithName(anOrigin: TCSSOrigin; const aName: TCSSString): integer;
     function FindStyleSheetWithElement(El: TCSSElement): TStyleSheet;
     property StyleSheetCount: integer read FStyleSheetCount;
     property StyleSheets[Index: integer]: TStyleSheet read GetStyleSheets;
@@ -444,6 +455,7 @@ function CompareCSSSharedRuleArrays(const Rules1, Rules2: TCSSSharedRuleArray):
 function CompareCSSSharedRuleLists(A, B: Pointer): integer;
 function CompareRulesArrayWithCSSSharedRuleList(RuleArray, SharedRuleList: Pointer): integer;
 
+
 implementation
 
 function ComparePointer(Data1, Data2: Pointer): integer;
@@ -570,6 +582,66 @@ end;
 { TCSSAttributeValues }
 
 procedure TCSSAttributeValues.SortValues;
+
+  procedure QuickSort(L, R : integer);
+  var
+    I, J, PivotIdx : integer;
+    AttrP: TCSSNumericalID;
+    V: TCSSAttributeValue;
+  begin
+    repeat
+      I := L;
+      J := R;
+      PivotIdx := L + ((R - L) shr 1); { same as ((L + R) div 2), but without the possibility of overflow }
+      AttrP := Values[PivotIdx].AttrID;
+      repeat
+        while (I < PivotIdx) and (AttrP > Values[i].AttrID) do
+          Inc(I);
+        while (J > PivotIdx) and (AttrP < Values[J].AttrID) do
+          Dec(J);
+        if I < J then
+        begin
+          V := Values[I];
+          Values[I] := Values[J];
+          Values[J] := V;
+          if PivotIdx = I then
+          begin
+            PivotIdx := J;
+            Inc(I);
+          end
+          else if PivotIdx = J then
+          begin
+            PivotIdx := I;
+            Dec(J);
+          end
+          else
+          begin
+            Inc(I);
+            Dec(J);
+          end;
+        end;
+      until I >= J;
+      // sort the smaller range recursively
+      // sort the bigger range via the loop
+      // Reasons: memory usage is O(log(n)) instead of O(n) and loop is faster than recursion
+      if (PivotIdx - L) < (R - PivotIdx) then
+      begin
+        if (L + 1) < PivotIdx then
+          QuickSort(L, PivotIdx - 1);
+        L := PivotIdx + 1;
+      end
+      else
+      begin
+        if (PivotIdx + 1) < R then
+          QuickSort(PivotIdx + 1, R);
+        if (L + 1) < PivotIdx then
+          R := PivotIdx - 1
+        else
+          exit;
+      end;
+    until L >= R;
+  end;
+
 var
   l: SizeInt;
   i, j: Integer;
@@ -582,8 +654,12 @@ begin
         if Values[i].AttrID>Values[j].AttrID then
           SwapValues(i,j);
   end else begin
-    // todo: quicksort
-    raise ECSSResolver.Create('20240628170311');
+    //for i:=0 to l-1 do
+    //  writeln('TCSSAttributeValues.SortValues ',i,' ',Values[i]<>nil);
+    QuickSort(0,l-1);
+    for i:=0 to l-2 do
+      if Values[i].AttrID>=Values[i+1].AttrID then
+        raise Exception.Create('20240816160749');
   end;
 end;
 
@@ -592,7 +668,7 @@ var
   A, B: TCSSAttributeValue;
   AttrId: TCSSNumericalID;
   Value: TCSSString;
-  Computed: Boolean;
+  aState: TCSSAttributeValue.TState;
 begin
   A:=Values[Index1];
   B:=Values[Index2];
@@ -605,9 +681,9 @@ begin
   A.Value:=B.Value;
   B.Value:=Value;
 
-  Computed:=A.Computed;
-  A.Computed:=B.Computed;
-  B.Computed:=Computed;
+  aState:=A.State;
+  A.State:=B.State;
+  B.State:=aState;
 end;
 
 function TCSSAttributeValues.IndexOf(AttrID: TCSSNumericalID): integer;
@@ -631,17 +707,41 @@ begin
   Result:=-1;
 end;
 
-function TCSSAttributeValues.GetValue(AttrDesc: TCSSAttributeDesc): TCSSString;
+procedure TCSSAttributeValues.SetComputedValue(AttrID: TCSSNumericalID; const aValue: TCSSString);
+
+  procedure AddNew;
+  var
+    Item: TCSSAttributeValue;
+    i, l: integer;
+  begin
+    l:=length(Values);
+    i:=l;
+    while (i>0) and (Values[i-1].AttrID>AttrID) do dec(i);
+    Item:=TCSSAttributeValue.Create;
+    Item.AttrID:=AttrID;
+    Item.State:=cavsComputed;
+    Item.Value:=aValue;
+    System.Insert(Item,Values,i);
+  end;
+
 var
   i: Integer;
 begin
-  i:=IndexOf(AttrDesc.Index);
-  if i>=0 then
-    Result:=Values[i].Value
-  else if AttrDesc.All then
-    Result:=AllValue
-  else
-    Result:='';
+  if AttrID<=CSSAttributeID_All then
+    raise Exception.Create('20240729084610');
+  if Values=nil then
+  begin
+    AddNew;
+  end else begin
+    i:=IndexOf(AttrID);
+    if i>=0 then
+    begin
+      Values[i].State:=cavsComputed;
+      Values[i].Value:=aValue;
+    end else begin
+      AddNew;
+    end;
+  end;
 end;
 
 destructor TCSSAttributeValues.Destroy;
@@ -767,6 +867,28 @@ begin
   end;
 end;
 
+procedure TCSSResolver.ClearElements;
+var
+  i: Integer;
+begin
+  FLogEntries.Clear;
+
+  ClearMerge;
+  ClearSharedRuleLists;
+
+  // clear layers
+  for i:=0 to length(FLayers)-1 do
+  begin
+    FLayers[i].ElementCount:=0;
+    FLayers[i].Elements:=nil;
+    FLayers[i].Name:='';
+  end;
+  FLayers:=nil;
+
+  for i:=0 to FStyleSheetCount-1 do
+    FreeAndNil(FStyleSheets[i].Element);
+end;
+
 procedure TCSSResolver.AddRule(aRule: TCSSRuleElement; Specifity: TCSSSpecifity
   );
 var
@@ -887,7 +1009,7 @@ begin
     FSharedRuleLists.Add(Result);
 
     // merge shared properties
-    InitMerge;
+    ClearMerge;
     for i:=0 to length(Result.Rules)-1 do
     begin
       RuleArr:=Result.Rules[i];
@@ -900,7 +1022,7 @@ begin
   end;
 end;
 
-procedure TCSSResolver.InitMerge;
+procedure TCSSResolver.ClearMerge;
 var
   i: Integer;
 begin
@@ -952,6 +1074,7 @@ var
   AttrP: PMergedAttribute;
 begin
   AttrP:=@FMergedAttributes[AttrID];
+  if AttrP^.Stamp<>FMergedAttributesStamp then exit;
   AttrP^.Stamp:=0;
   if FMergedAttributeFirst=AttrID then
     FMergedAttributeFirst:=AttrP^.Next;
@@ -1328,7 +1451,7 @@ begin
         Result:=CSSSpecifityNoMatch;
       CSSAttributeID_ID,
       CSSAttributeID_Class:
-        // basic CSS attributes are always defined
+        // id and class are always defined
         Result:=CSSSpecifityClass;
       CSSAttributeID_All:
         // special CSS attributes without a value
@@ -2067,7 +2190,7 @@ procedure TCSSResolver.LoadSharedMergedAttributes(
 var
   i: Integer;
 begin
-  InitMerge;
+  ClearMerge;
   FMergedAllDecl:=SharedMerged.AllDecl;
   FMergedAllSpecifity:=SharedMerged.AllSpecifity;
   for i:=0 to length(SharedMerged.Values)-1 do
@@ -2126,7 +2249,7 @@ begin
       if TCSSResolverParser.IsWhiteSpace(Value) then
         RemoveMergedAttribute(AttrID)
       else
-        AttrP^.Complete:=KeyData.Complete or (Pos('var(',Value)<1);
+        AttrP^.Complete:=KeyData.Complete;
     end;
     AttrID:=NextAttrID;
   end;
@@ -2176,31 +2299,35 @@ begin
     if Assigned(AttrDesc.OnSplitShorthand) then
     begin
       RemoveMergedAttribute(AttrID);
-      if AttrP^.Value<>'' then
+      if AttrP^.Value>'' then
       begin
         // replace shorthand with longhands, keep already set longhands
         LHAttrIDs:=[];
         LHValues:=[];
-        AttrDesc.OnSplitShorthand(Self,AttrDesc,AttrP^.Value,LHAttrIDs,LHValues);
-        for i:=0 to length(LHAttrIDs)-1 do
+        InitParseAttr(AttrDesc,nil,AttrP^.Value);
+        if not (CurComp.Kind in [rvkNone,rvkInvalid]) then
         begin
-          SubAttrID:=LHAttrIDs[i];
-          SubAttrDesc:=GetAttributeDesc(SubAttrID);
-          if SubAttrDesc=nil then
-            raise ECSSResolver.Create('20240709194135');
-          if SubAttrDesc.OnSplitShorthand<>nil then
-            raise ECSSResolver.Create('20240709194634');
-          SubAttrP:=@FMergedAttributes[SubAttrID];
-          if SubAttrP^.Stamp=FMergedAttributesStamp then
+          AttrDesc.OnSplitShorthand(Self,LHAttrIDs,LHValues);
+          for i:=0 to length(LHAttrIDs)-1 do
           begin
-            // longhand already exists -> keep
-            if SubAttrP^.Specifity<AttrP^.Specifity then
-              raise ECSSResolver.Create('20240709194537');
-          end else begin
-            SetMergedAttribute(SubAttrID,AttrP^.Specifity,nil);
-            SubAttrP^.Value:=LHValues[i];
-            SubAttrP^.Complete:=false;
-            // Note: if NextAttrID=0 then this was the last shorthand
+            SubAttrID:=LHAttrIDs[i];
+            SubAttrDesc:=GetAttributeDesc(SubAttrID);
+            if SubAttrDesc=nil then
+              raise ECSSResolver.Create('20240709194135');
+            if SubAttrDesc.OnSplitShorthand<>nil then
+              raise ECSSResolver.Create('20240709194634');
+            SubAttrP:=@FMergedAttributes[SubAttrID];
+            if (SubAttrP^.Stamp=FMergedAttributesStamp) and (SubAttrP^.Specifity>=AttrP^.Specifity) then
+            begin
+              // longhand already exists -> keep
+            end else begin
+              SetMergedAttribute(SubAttrID,AttrP^.Specifity,nil);
+              SubAttrP^.Value:=LHValues[i];
+              if SubAttrP^.Value='' then
+                SubAttrP^.Value:=SubAttrDesc.InitialValue;
+              SubAttrP^.Complete:=false;
+              // Note: if NextAttrID=0 then this was the last shorthand
+            end;
           end;
         end;
       end;
@@ -2221,8 +2348,12 @@ begin
   // all
   if FMergedAllDecl<>nil then
   begin
-    // todo parse FMergedAllDecl and set Result.All
-    raise ECSSResolver.Create('20240628165049');
+    // set Result.AllValue
+    InitParseAttr(CSSRegistry.Attributes[CSSAttributeID_All],nil,GetDeclarationValue(FMergedAllDecl));
+    if (CurComp.Kind=rvkKeyword) and IsBaseKeyword(CurComp.KeywordID) then
+    begin
+      Result.AllValue:=CurComp.KeywordID;
+    end;
   end;
 
   // count and allocate attributes
@@ -2246,9 +2377,9 @@ begin
     Result.Values[Cnt]:=AttrValue;
     AttrValue.AttrID:=AttrID;
     AttrValue.Value:=AttrP^.Value;
-    AttrValue.Computed:=false;
-    inc(Cnt);
+    //writeln('TCSSResolver.CreateValueList ',Cnt,' ',AttrID,' "',AttrValue.Value,'"');
     AttrID:=AttrP^.Next;
+    inc(Cnt);
   end;
 
   // sort
@@ -2335,9 +2466,9 @@ begin
   end;
 end;
 
-function TCSSResolver.ParseInlineStyle(const Src: TCSSString): TCSSElement;
+function TCSSResolver.ParseInlineStyle(const Src: TCSSString): TCSSRuleElement;
 begin
-  Result:=ParseCSSSource(Src,true);
+  Result:=ParseCSSSource(Src,true) as TCSSRuleElement;
 end;
 
 function TCSSResolver.GetElPath(El: TCSSElement): TCSSString;
@@ -2362,8 +2493,6 @@ end;
 
 procedure TCSSResolver.Clear;
 begin
-  ClearSharedRuleLists;
-  FLogEntries.Clear;
   ClearStyleSheets;
 end;
 
@@ -2372,13 +2501,19 @@ var
   OldLen, NewLen: TCSSNumericalID;
   i: Integer;
 begin
-  CSSRegistry.ConsistencyCheck;;
+  if CSSRegistry.Modified then
+  begin
+    CSSRegistry.ConsistencyCheck;
+    CSSRegistry.Modified:=false;
+  end;
+
   FMergedAttributesStamp:=1;
   for i:=0 to length(FMergedAttributes)-1 do
     FMergedAttributes[i].Stamp:=0;
   OldLen:=length(FMergedAttributes);
   NewLen:=OldLen;
-  if CSSRegistry.AttributeCount>NewLen then NewLen:=CSSRegistry.AttributeCount;
+  if CSSRegistry.AttributeCount>NewLen then
+    NewLen:=CSSRegistry.AttributeCount;
   if NewLen>OldLen then
   begin
     SetLength(FMergedAttributes,NewLen);
@@ -2444,18 +2579,23 @@ begin
     Result:=CSSRegistry.Attributes[AttrId];
 end;
 
+function TCSSResolver.GetDeclarationValue(Decl: TCSSDeclarationElement): TCSSString;
+var
+  KeyData: TCSSAttributeKeyData;
+begin
+  Result:='';
+  if Decl=nil then exit;
+  if Decl.KeyCount=0 then exit;
+  KeyData:=TCSSAttributeKeyData(Decl.Keys[0].CustomData);
+  if KeyData=nil then exit;
+  Result:=KeyData.Value;
+end;
+
 procedure TCSSResolver.ClearStyleSheets;
 var
   i: Integer;
 begin
-  // clear layers
-  for i:=0 to length(FLayers)-1 do
-  begin
-    FLayers[i].ElementCount:=0;
-    FLayers[i].Elements:=nil;
-    FLayers[i].Name:='';
-  end;
-  FLayers:=nil;
+  ClearElements;
 
   // clear stylesheets
   for i:=0 to FStyleSheetCount-1 do
@@ -2466,11 +2606,21 @@ begin
   FStyleSheetCount:=0;
 end;
 
-function TCSSResolver.AddStyleSheet(const aSource, aFilename: TCSSString;
-  anOrigin: TCSSOrigin): TStyleSheet;
+function TCSSResolver.AddStyleSheet(anOrigin: TCSSOrigin; const aName: TCSSString;
+  const aSource: TCSSString): TStyleSheet;
 var
-  Cnt: SizeInt;
+  Cnt, i: SizeInt;
 begin
+  if aName>'' then
+  begin
+    i:=IndexOfStyleSheetWithName(anOrigin,aName);
+    if i>=0 then
+    begin
+      ReplaceStyleSheet(i,aSource);
+      exit;
+    end;
+  end;
+
   Cnt:=length(FStyleSheets);
   if Cnt=FStyleSheetCount then
   begin
@@ -2490,9 +2640,9 @@ begin
   inc(FStyleSheetCount);
 
   with Result do begin
-    Source:=aSource;
-    Filename:=aFilename;
+    Name:=aName;
     Origin:=anOrigin;
+    Source:=aSource;
     Parsed:=false;
     if Element<>nil then
       FreeAndNil(Element);
@@ -2501,6 +2651,21 @@ begin
   ParseSource(FStyleSheetCount-1);
 end;
 
+procedure TCSSResolver.ReplaceStyleSheet(Index: integer; const NewSource: TCSSString);
+var
+  Sheet: TStyleSheet;
+begin
+  Sheet:=StyleSheets[Index];
+  if NewSource=Sheet.Source then exit;
+  ClearMerge;
+  ClearSharedRuleLists;
+  FreeAndNil(Sheet.Element);
+  Sheet.Parsed:=false;
+  Sheet.Source:=NewSource;
+
+  ParseSource(Index);
+end;
+
 function TCSSResolver.IndexOfStyleSheetWithElement(El: TCSSElement): integer;
 var
   aParent: TCSSElement;
@@ -2518,6 +2683,20 @@ begin
       exit(i);
 end;
 
+function TCSSResolver.IndexOfStyleSheetWithName(anOrigin: TCSSOrigin; const aName: TCSSString
+  ): integer;
+var
+  Sheet: TStyleSheet;
+begin
+  for Result:=0 to FStyleSheetCount-1 do
+  begin
+    Sheet:=FStyleSheets[Result];
+    if (Sheet.Origin=anOrigin) and (aName=Sheet.Name) then
+      exit;
+  end;
+  Result:=-1;
+end;
+
 function TCSSResolver.FindStyleSheetWithElement(El: TCSSElement): TStyleSheet;
 var
   i: Integer;

File diff suppressed because it is too large
+ 667 - 124
packages/fcl-css/src/fpcssresparser.pas


+ 141 - 40
packages/fcl-css/src/fpcsstree.pp

@@ -68,51 +68,152 @@ Type
   TCSSStringArray = array of TCSSString;
 
   TCSSUnit = (
-    cuNONE,// no unit, only allowed for 0
-    cuPX,  // pixels
-    cuCM,  // centimeters
-    cuMM,  // milimeters
-    cuQ,   // quarter-milimeters
-    cuIN,  // inches
-    cuPC,  // picas
-    cuPT,  // points
-    cuPERCENT, // percentage %
-    cuEM,  // relative to element's font-size
-    cuREM, // relative to parent's font-size
-    cuVW,  // relative to viewport's width
-    cuVH,  // relative to viewport's height
-    cuFR,  // fraction of flex space
-    cuDEG, // degrees, full circle is 360deg
-    cuGRAD,// gradians, full circle is 400grad
-    cuRAD, // radians, full circle is (2*pi)rad
-    cuTURN // turns, full circle is 1turn
+    cuNone, // no unit
+    // absolute lengths
+    cuPX,   // pixels
+    cuCM,   // centimeters
+    cuMM,   // milimeters
+    cuQ,    // quarter-milimeters
+    cuIn,   // inches
+    cuPT,   // points (1pt = 1/72 of 1in)
+    cuPC,   // picas (1pc = 12 pt)
+    // percentage
+    cuPercent, // percentage, context sensitive
+    // relative to element's font
+    cuEM,   // relative to the height of char "M" of element's font
+    cuEX,   // relative to the height of char "x" of element's font
+    cuCap,  // cap height, relative to nominal height of capital letters
+    cuCh,   // relative to the width of the "0" (zero)
+    cuIC,   // advance measure of the "水" glyph (CJK water ideograph, U+6C34)
+    cuLH,   // line-height
+    // relative to root's font
+    cuREM,  // root-em, as EM, except the font of the root element
+    cuREX,  // root-ex
+    cuRCap, // root-cap height
+    cuRCh,  // root-ch
+    cuRIC,  // root-ic
+    cuRLH,  // root-line-height
+    // relative to default viewport size
+    cuVW,   // relative to 1% of the width of the viewport
+    cuVH,   // relative to 1% of the height of the viewport
+    cuVMax, // relative to 1% of viewport's larger dimension
+    cuVMin, // relative to 1% of viewport's smaller dimension
+    cuVB,   // relative to 1% of viewport's block axis
+    cuVI,   // relative to 1% of viewport's inline axis
+    // relative to small viewport (when e.g. viewport is shrunk to show browser interface)
+    cuSVW,  // small-vw
+    cuSVH,  // small-vh
+    cuSVMax,// small-vmax
+    cuSVMin,// small-vmin
+    cuSVB,  // small-vb
+    cuSVI,  // small-vi
+    // relative to large viewport (when e.g. browser hides interface and viewport is expanded)
+    cuLVW,  // large-vw
+    cuLVH,  // large-vh
+    cuLVMax,// large-vmax
+    cuLVMin,// large-vmin
+    cuLVB,  // large-vb
+    cuLVI,  // large-vi
+    // relative to dynamic viewport size aka current size
+    cuDVW,  // dynamic-vw
+    cuDVH,  // dynamic-vh
+    cuDVMax,// dynamic-vmax
+    cuDVMin,// dynamic-vmin
+    cuDVB,  // dynamic-vb
+    cuDVI,  // dynamic-vi
+    // container queries
+    cuCQW,  // relative to 1% of container's width
+    cuCQH,  // relative to 1% of container's height
+    cuCQB,  // relative to 1% of container's block axis dimension
+    cuCQI,  // relative to 1% of container's inline axis dimension
+    cuCQMin,// relative to 1% of container's smaller dimension
+    cuCQMax,// relative to 1% of container's larger dimension
+    // angles
+    cuDeg,  // degrees, full circle is 360deg
+    cuGrad, // gradians, full circle is 400grad
+    cuRad,  // radians, full circle is (2*pi)rad
+    cuTurn, // turns, full circle is 1turn
+    // special
+    cuFr    // fraction of flex space
     );
   TCSSUnits = set of TCSSUnit;
 const
-  cuAllAbsoluteLengths = [cuPX,cuCM,cuMM,cuQ,cuIN,cuPC,cuPT];
-  cuAllRelativeFontSize = [cuEM,cuREM];
-  cuAllLengths = cuAllAbsoluteLengths+cuAllRelativeFontSize;
-  cuAllAngles = [cuDEG,cuGRAD,cuRAD,cuTURN];
+  cuAllAbsoluteLengths = [cuPX,cuCM,cuMM,cuQ,cuIn,cuPT,cuPC];
+  cuAllViewportLengths = [cuVW,cuVH,cuVMax,cuVMin,cuVB,cuVI,
+                          cuSVW,cuSVH,cuSVMax,cuSVMin,cuSVB,cuSVI,
+                          cuLVW,cuLVH,cuLVMax,cuLVMin,cuLVB,cuLVI,
+                          cuDVW,cuDVH,cuDVMax,cuDVMin,cuDVB,cuDVI];
+  cuAllRelativeFontSize = [cuEM,cuEX,cuCap,cuCh,cuIC,cuLH,
+                           cuREM,cuREX,cuRCap,cuRCh,cuRIC,cuRLH];
+  cuAllLengths = cuAllAbsoluteLengths+cuAllViewportLengths+cuAllRelativeFontSize;
+  cuAllLengthsAndPercent = cuAllLengths+[cuPercent];
+  cuAllAngles = [cuDeg,cuGrad,cuRad,cuTurn];
 
   CSSUnitNames: array[TCSSUnit] of TCSSString = (
-    '',    // no unit
-    'px',  // pixels
-    'cm',  // centimeters
-    'mm',  // milimeters
-    'Q',   // quarter-milimeters
-    'in',  // inches
-    'pc',  // picas
-    'pt',  // points
-    '%',   // percentage
-    'em',  // elements font-size
-    'rem', // relative-em
-    'vw',  // viewport-width
-    'vh',  // viewport-height
-    'fr',  // fraction
-    'deg', // degrees
-    'grad',// gradians
-    'rad', // radians
-    'turn' // turns
+    '',     // no unit
+    // absolute lengths
+    'px',   // pixels
+    'cm',   // centimeters
+    'mm',   // milimeters
+    'Q',    // quarter-milimeters, Big Q!
+    'in',   // inches
+    'pt',   // points
+    'pc',   // picas
+    // %
+    '%',    // percentage
+    // relative to element's font
+    'em',   // elements font-size
+    'ex',   // elements height of "x"
+    'cap',  // cap-height
+    'ch',   // character "0"
+    'ic',   // CJK water ideograph
+    'lh',   // line-height
+    // relative to root's font
+    'rem',  // root-em
+    'rex',  // root-ex
+    'rcap', // root-cap-height
+    'rch',  // root-character "0"
+    'ric',  // root-ic
+    'rlh',  // root-line-height
+    // relative to viewport
+    'vw',   // viewport-width
+    'vh',   // viewport-height
+    'vmax', // viewport larger dimension
+    'vmin', // viewport smaller dimension
+    'vb',   // viewport block axis size
+    'vi',   // viewport inline axis size
+    'svw',  // small-vw
+    'svh',  // small-vh
+    'svmax',// small-vmax
+    'svmin',// small-vmin
+    'svb',  // small-vb
+    'svi',  // small-vi
+    'lvw',  // large-vw
+    'lvh',  // large-vh
+    'lvmax',// large-vmax
+    'lvmin',// large-vmin
+    'lvb',  // large-vb
+    'lvi',  // large-vi
+    'dvw',  // dynamic-vw
+    'dvh',  // dynamic-vh
+    'dvmax',// dynamic-vmax
+    'dvmin',// dynamic-vmin
+    'dvb',  // dynamic-vb
+    'dvi',  // dynamic-vi
+    // container queries
+    'cqw',  // container's width
+    'cqh',  // container's height
+    'cqb',  // container's block axis dimension
+    'cqi',  // container's inline axis dimension
+    'cqmin',// container's smaller dimension
+    'cqmax',// container's larger dimension
+    // angles
+    'deg',  // degrees
+    'grad', // gradians
+    'rad',  // radians
+    'turn', // turns
+    // special
+    'fr'    // fraction
     );
 
 type

+ 124 - 137
packages/fcl-css/tests/tccssresolver.pp

@@ -137,14 +137,13 @@ type
   TDemoCSSRegistry = class(TCSSRegistry)
   private
     // check attribute declarations for validity
-    procedure OnCheck_BorderColor(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
-    procedure OnCheck_BorderWidth(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
-    procedure OnCheck_Border(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
-    procedure OnCheck_Direction(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
-    procedure OnCheck_Display(Resolver: TCSSBaseResolver;
-      Data: TCSSAttributeKeyData);
-    procedure OnCheck_LeftTop(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
-    procedure OnCheck_WidthHeight(Resolver: TCSSBaseResolver; Data: TCSSAttributeKeyData);
+    function OnCheck_BorderColor(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_BorderWidth(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_Border(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_Direction(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_Display(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_LeftTop(Resolver: TCSSBaseResolver): boolean;
+    function OnCheck_WidthHeight(Resolver: TCSSBaseResolver): boolean;
     // clean up and normalize attribute values
     procedure OnCompute_Direction(Resolver: TCSSResolver; Node: TDemoNode;
       Value: TCSSAttributeValue);
@@ -153,9 +152,8 @@ type
     procedure OnCompute_WidthHeight(Resolver: TCSSResolver; Node: TDemoNode;
       Value: TCSSAttributeValue);
     // split shorthands into longhands
-    procedure OnSplit_Border(Resolver: TCSSBaseResolver; {%H-}Desc: TCSSAttributeDesc;
-      const aValue: TCSSString; var AttrIDs: TCSSNumericalIDArray; var
-      Values: TCSSStringArray);
+    procedure OnSplit_Border(Resolver: TCSSBaseResolver;
+      var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray);
   public
     DemoAttrIDBase: TCSSNumericalID;
     DemoPseudoClassIDBase: TCSSNumericalID;
@@ -166,24 +164,22 @@ type
     DemoTypes: array[TDemoElementType] of TDemoCSSTypeDesc;
 
     // keywords
-    kwFirstColor,
     kwRed,
     kwGreen,
     kwBlue,
     kwWhite,
     kwBlack,
     kwBlock,
-    kwLastColor,
     kwInline_Block,
     kwLTR,
     kwRTL: TCSSNumericalID;
 
     // check parameters
-    CheckParams_BorderWidth: TCSSCheckAttrParams_Float;
-    CheckParams_DirectionAllowedKeywordIDs: TCSSNumericalIDArray;
-    CheckParams_DisplayAllowedKeywordIDs: TCSSNumericalIDArray;
-    CheckParams_LeftTop: TCSSCheckAttrParams_Float;
-    CheckParams_WidthHeight: TCSSCheckAttrParams_Float;
+    Chk_BorderWidth: TCSSCheckAttrParams_Dimension;
+    Chk_DirectionAllowedKeywordIDs: TCSSNumericalIDArray;
+    Chk_DisplayAllowedKeywordIDs: TCSSNumericalIDArray;
+    Chk_LeftTop: TCSSCheckAttrParams_Dimension;
+    Chk_WidthHeight: TCSSCheckAttrParams_Dimension;
 
     constructor Create;
     function AddDemoAttr(Attr: TDemoNodeAttribute): TDemoCSSAttributeDesc;
@@ -529,6 +525,7 @@ begin
     +'  height: 6E+1rem;'
     +'}';
   Div1:=TDemoDiv.Create(nil);
+  Div1.Name:='Div1';
   Div1.Parent:=Doc.Root;
 
   Doc.ApplyStyle;
@@ -1832,7 +1829,7 @@ procedure TDemoDocument.ApplyStyle;
 begin
   ApplyTypeStyles;
 
-  CSSResolver.AddStyleSheet(Style,'test.css',cssoAuthor);
+  CSSResolver.AddStyleSheet(cssoAuthor,'test.css',Style);
   CSSResolver.Init;
   Traverse(Root);
 end;
@@ -1861,8 +1858,8 @@ var
       ParentSrc:=ParentNodeClass.GetCSSTypeStyle;
       if Src=ParentSrc then exit;
     end;
-    //writeln('AddTypeStyle ',NodeClass.ClassName);
-    FCSSResolver.AddStyleSheet(Src,NodeClass.ClassName,cssoUserAgent);
+    //writeln('AddTypeStyle ',NodeClass.ClassName,' [',Src,']');
+    FCSSResolver.AddStyleSheet(cssoUserAgent,NodeClass.ClassName,Src);
   end;
 
   procedure CollectTypeStyles(Node: TDemoNode);
@@ -1885,161 +1882,138 @@ end;
 
 { TDemoCSSRegistry }
 
-procedure TDemoCSSRegistry.OnCheck_Border(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
+function TDemoCSSRegistry.OnCheck_Border(Resolver: TCSSBaseResolver): boolean;
 var
-  p: PCSSChar;
-  ResValue: TCSSResValue;
   HasWidth, HasColor: Boolean;
 begin
   HasWidth:=false;
   HasColor:=false;
-  p:=PCSSChar(Data.Value);
   repeat
-    if not Resolver.ReadValue(p,ResValue) then break;
-    case ResValue.Kind of
+    case Resolver.CurComp.Kind of
     rvkFloat:
       if not HasWidth then
-        HasWidth:=ResValue.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths);
+        HasWidth:=Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths);
     rvkKeyword:
       if not HasColor then
-        HasColor:=(ResValue.KeywordID>=kwFirstColor) and (ResValue.KeywordID<=kwLastColor);
+        HasColor:=(Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor);
     end;
-  until false;
-  Data.Invalid:=(not HasWidth) and (not HasColor);
+  until not Resolver.ReadNext;
+  Result:=HasWidth or HasColor;
 end;
 
-procedure TDemoCSSRegistry.OnCheck_BorderColor(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  HasColor: Boolean;
-  p: PCSSChar;
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_BorderColor(Resolver: TCSSBaseResolver): boolean;
 begin
-  HasColor:=false;
-  p:=PCSSChar(Data.Value);
-  repeat
-    if not Resolver.ReadValue(p,ResValue) then break;
-    case ResValue.Kind of
-    rvkKeyword:
-      if not HasColor then
-        HasColor:=(ResValue.KeywordID>=kwFirstColor) and (ResValue.KeywordID<=kwLastColor);
-    end;
-  until false;
-  Data.Invalid:=not HasColor;
+  Result:=Resolver.CheckAttribute_Color([]);
 end;
 
-procedure TDemoCSSRegistry.OnCheck_BorderWidth(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_BorderWidth(Resolver: TCSSBaseResolver): boolean;
 begin
-  Resolver.CheckAttribute_Dimension(Data,CheckParams_BorderWidth,ResValue);
+  Result:=Resolver.CheckAttribute_Dimension(Chk_BorderWidth);
 end;
 
 procedure TDemoCSSRegistry.OnSplit_Border(Resolver: TCSSBaseResolver;
-  Desc: TCSSAttributeDesc; const aValue: TCSSString;
   var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray);
 var
-  HasWidth, HasColor: Boolean;
-  p: PCSSChar;
-  ResValue: TCSSResValue;
+  aWidth, aColor: TCSSString;
 begin
-  HasWidth:=false;
-  HasColor:=false;
-  p:=PCSSChar(aValue);
+  aWidth:='';
+  aColor:='';
   repeat
-    if not Resolver.ReadValue(p,ResValue) then break;
-    case ResValue.Kind of
+    case Resolver.CurComp.Kind of
     rvkFloat:
-      if not HasWidth then begin
-        HasWidth:=ResValue.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths);
-        if HasWidth then
-        begin
-          System.Insert(DemoAttrs[naBorderWidth].Index,AttrIDs,length(AttrIDs));
-          System.Insert(ResValue.FloatAsString,Values,length(Values));
-        end;
+      if aWidth='' then begin
+        if Resolver.CurComp.FloatUnit in ([cuNONE,cuPERCENT]+cuAllLengths) then
+          aWidth:=Resolver.CurComp.FloatAsString;
       end;
     rvkKeyword:
-      if not HasColor then
+      if aColor='' then
       begin
-        HasColor:=(ResValue.KeywordID>=kwFirstColor) and (ResValue.KeywordID<=kwLastColor);
-        if HasColor then
-        begin
-          System.Insert(DemoAttrs[naBorderColor].Index,AttrIDs,length(AttrIDs));
-          System.Insert(Resolver.CSSRegistry.Keywords[ResValue.KeywordID],Values,length(Values));
-        end;
+        if (Resolver.CurComp.KeywordID>=kwFirstColor) and (Resolver.CurComp.KeywordID<=kwLastColor) then
+          aColor:=Keywords[Resolver.CurComp.KeywordID];
       end;
     end;
-  until false;
+  until not Resolver.ReadNext;
+  SetLength(AttrIDs,2);
+  SetLength(Values,2);
+  AttrIDs[0]:=DemoAttrs[naBorderWidth].Index;
+  Values[0]:=aWidth;
+  AttrIDs[1]:=DemoAttrs[naBorderColor].Index;
+  Values[1]:=aColor;
 end;
 
-procedure TDemoCSSRegistry.OnCheck_Direction(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_Direction(Resolver: TCSSBaseResolver): boolean;
 begin
-  Resolver.CheckAttribute_Keyword(Data,CheckParams_DirectionAllowedKeywordIDs,ResValue);
+  Result:=Resolver.CheckAttribute_Keyword(Chk_DirectionAllowedKeywordIDs);
 end;
 
-procedure TDemoCSSRegistry.OnCheck_Display(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_Display(Resolver: TCSSBaseResolver): boolean;
 begin
-  Resolver.CheckAttribute_Keyword(Data,CheckParams_DisplayAllowedKeywordIDs,ResValue);
+  Result:=Resolver.CheckAttribute_Keyword(Chk_DisplayAllowedKeywordIDs);
 end;
 
-procedure TDemoCSSRegistry.OnCheck_LeftTop(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_LeftTop(Resolver: TCSSBaseResolver): boolean;
 begin
-  Resolver.CheckAttribute_Dimension(Data,CheckParams_LeftTop,ResValue);
+  Result:=Resolver.CheckAttribute_Dimension(Chk_LeftTop);
 end;
 
-procedure TDemoCSSRegistry.OnCheck_WidthHeight(Resolver: TCSSBaseResolver;
-  Data: TCSSAttributeKeyData);
-var
-  ResValue: TCSSResValue;
+function TDemoCSSRegistry.OnCheck_WidthHeight(Resolver: TCSSBaseResolver): boolean;
 begin
-  writeln('AAA1 TDemoCSSRegistry.OnCheck_WidthHeight "',Data.Value,'"');
-  Resolver.CheckAttribute_Dimension(Data,CheckParams_WidthHeight,ResValue);
+  Result:=Resolver.CheckAttribute_Dimension(Chk_WidthHeight);
 end;
 
 procedure TDemoCSSRegistry.OnCompute_Direction(Resolver: TCSSResolver;
   Node: TDemoNode; Value: TCSSAttributeValue);
 var
-  ResValue: TCSSResValue;
+  Invalid: boolean;
 begin
-  if Resolver.ReadAttribute_Keyword(Value.Value,Value.Invalid,CheckParams_DirectionAllowedKeywordIDs,ResValue) then
-    Value.Value:=TDemoNode.CSSRegistry.Keywords[ResValue.KeywordID]
-  else
+  if Resolver.ReadAttribute_Keyword(Invalid,Chk_DirectionAllowedKeywordIDs) then
+  begin
+    Value.Value:=Keywords[Resolver.CurComp.KeywordID];
+    Value.State:=cavsComputed;
+  end
+  else begin
     Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
   if Node=nil then ;
 end;
 
 procedure TDemoCSSRegistry.OnCompute_LeftTop(Resolver: TCSSResolver;
   Node: TDemoNode; Value: TCSSAttributeValue);
 var
-  ResValue: TCSSResValue;
+  Invalid: boolean;
 begin
-  if Resolver.ReadAttribute_Dimension(Value.Value,Value.Invalid,CheckParams_LeftTop,ResValue) then
-    Value.Value:=ResValue.FloatAsString
-  else
+  if Resolver.ReadAttribute_Dimension(Invalid,Chk_LeftTop) then
+  begin
+    case Resolver.CurComp.Kind of
+    rvkFloat:
+      Value.Value:=Resolver.CurComp.FloatAsString;
+    rvkKeyword:
+      Value.Value:=Keywords[Resolver.CurComp.KeywordID];
+    end;
+    Value.State:=cavsComputed;
+  end
+  else begin
     Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
   if Node=nil then ;
 end;
 
 procedure TDemoCSSRegistry.OnCompute_WidthHeight(Resolver: TCSSResolver;
   Node: TDemoNode; Value: TCSSAttributeValue);
 var
-  ResValue: TCSSResValue;
+  Invalid: boolean;
 begin
-  if Resolver.ReadAttribute_Dimension(Value.Value,Value.Invalid,CheckParams_WidthHeight,ResValue) then
-    Value.Value:=ResValue.FloatAsString
-  else
+  if Resolver.ReadAttribute_Dimension(Invalid,Chk_WidthHeight) then
+  begin
+    Value.Value:=Resolver.CurComp.FloatAsString;
+    Value.State:=cavsComputed;
+  end
+  else begin
     Value.Value:='invalid';
+    Value.State:=cavsInvalid;
+  end;
   if Node=nil then ;
 end;
 
@@ -2118,8 +2092,8 @@ begin
 
   // border-width
   DemoAttrs[naBorderWidth].OnCheck:=@OnCheck_BorderWidth;
-  CheckParams_BorderWidth.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  CheckParams_BorderWidth.AllowFrac:=true;
+  Chk_BorderWidth.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_BorderWidth.AllowFrac:=true;
 
   // border shorthand
   SetCompProps(naBorder,[naBorderColor,naBorderWidth]);
@@ -2128,29 +2102,29 @@ begin
 
   // direction
   DemoAttrs[naDirection].OnCheck:=@OnCheck_Direction;
-  CheckParams_DirectionAllowedKeywordIDs:=[kwLTR,kwRTL];
+  Chk_DirectionAllowedKeywordIDs:=[kwLTR,kwRTL];
   DemoAttrs[naDirection].OnCompute:=@OnCompute_Direction;
 
   // display
   DemoAttrs[naDisplay].OnCheck:=@OnCheck_Display;
-  CheckParams_DisplayAllowedKeywordIDs:=[kwBlock,kwInline_Block];
+  Chk_DisplayAllowedKeywordIDs:=[kwBlock,kwInline_Block];
 
   // left, top
   DemoAttrs[naLeft].OnCheck:=@OnCheck_LeftTop;
   DemoAttrs[naLeft].OnCompute:=@OnCompute_LeftTop;
   DemoAttrs[naTop].OnCheck:=@OnCheck_LeftTop;
   DemoAttrs[naTop].OnCompute:=@OnCompute_LeftTop;
-  CheckParams_LeftTop.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  CheckParams_LeftTop.AllowNegative:=true;
-  CheckParams_LeftTop.AllowFrac:=true;
+  Chk_LeftTop.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_LeftTop.AllowNegative:=true;
+  Chk_LeftTop.AllowFrac:=true;
 
   // width, height
   DemoAttrs[naWidth].OnCheck:=@OnCheck_WidthHeight;
   DemoAttrs[naWidth].OnCompute:=@OnCompute_WidthHeight;
   DemoAttrs[naHeight].OnCheck:=@OnCheck_WidthHeight;
   DemoAttrs[naHeight].OnCompute:=@OnCompute_WidthHeight;
-  CheckParams_WidthHeight.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
-  CheckParams_WidthHeight.AllowFrac:=true;
+  Chk_WidthHeight.AllowedUnits:=[cuNONE,cuPERCENT]+cuAllLengths;
+  Chk_WidthHeight.AllowFrac:=true;
 end;
 
 function TDemoCSSRegistry.AddDemoAttr(Attr: TDemoNodeAttribute
@@ -2188,9 +2162,14 @@ end;
 function TDemoNode.GetAttribute(DemoAttr: TDemoNodeAttribute): TCSSString;
 var
   AttrDesc: TDemoCSSAttributeDesc;
+  i: Integer;
 begin
-  AttrDesc:=TDemoNode.CSSRegistry.DemoAttrs[DemoAttr];
-  Result:=Values.GetValue(AttrDesc);
+  AttrDesc:=CSSRegistry.DemoAttrs[DemoAttr];
+  i:=Values.IndexOf(AttrDesc.Index);
+  if i>=0 then
+    Result:=Values.Values[i].Value
+  else
+    Result:='';
 end;
 
 function TDemoNode.GetNodeCount: integer;
@@ -2302,26 +2281,34 @@ begin
   Resolver.Compute(Self,InlineStyleElement,Rules,Values);
 
   {$IFDEF VerboseCSSResolver}
-  writeln('TDemoNode.ApplyCSS ',Name,' length(Values)=',length(Values.Values),' All="',Values.AllValue,'"');
-  for i:=0 to length(Values.Values)-1 do
-    writeln('TDemoNode.ApplyCSS ',Name,' resolved ',Values.Values[i].AttrID,':="',Values.Values[i].Value,'"');
+  writeln('TDemoNode.ApplyCSS ',Name,' length(Values)=',length(Values.Values),' All="',CSSRegistry.Keywords[Values.AllValue],'"');
+  for i:=0 to length(Values.Values)-1 do begin
+    AttrID:=Values.Values[i].AttrID;
+    writeln('TDemoNode.ApplyCSS ',Name,' resolved ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',Values.Values[i].Value,'"');
+  end;
   {$ENDIF}
   // compute values
   for i:=0 to length(Values.Values)-1 do
   begin
     CurValue:=Values.Values[i];
-    if CurValue.Computed then
-    begin
-    end else begin
-      AttrID:=CurValue.AttrID;
-      AttrDesc:=Resolver.CSSRegistry.Attributes[AttrID] as TDemoCSSAttributeDesc;
-      if AttrDesc.OnCompute<>nil then
-      begin
-        AttrDesc.OnCompute(Resolver,Self,CurValue);
-        {$IFDEF VerboseCSSResolver}
-        writeln('TDemoNode.ApplyCSS ',Name,' computed ',AttrID,':="',CurValue.Value,'"');
-        {$ENDIF}
-      end;
+    case CurValue.State of
+      cavsSource, cavsBaseKeywords:
+        begin
+          AttrID:=CurValue.AttrID;
+          AttrDesc:=CSSRegistry.Attributes[AttrID] as TDemoCSSAttributeDesc;
+          if AttrDesc.OnCompute<>nil then
+          begin
+            Resolver.CurComp.EndP:=PChar(CurValue.Value);
+            Resolver.ReadNext;
+            AttrDesc.OnCompute(Resolver,Self,CurValue);
+            {$IFDEF VerboseCSSResolver}
+            writeln('TDemoNode.ApplyCSS ',Name,' computed ',CSSRegistry.Attributes[AttrID].Name,'/',AttrID,':="',CurValue.Value,'"');
+            {$ENDIF}
+          end else
+            CurValue.State:=cavsComputed;
+        end;
+      cavsComputed: ;
+      cavsInvalid: ;
     end;
   end;
 end;

Some files were not shown because too many files changed in this diff