浏览代码

added CSSRegistry, check CSS attribute validity only once, auto add CSS of all types, split shorthands in resolver

mattias 1 年之前
父节点
当前提交
a301f732d8

+ 116 - 56
src/base/fcl-css/fpcssparser.pp

@@ -19,6 +19,7 @@ unit fpCSSParser;
 {$mode ObjFPC}{$H+}
 {$IF FPC_FULLVERSION>30300}
 {$WARN 6060 off} // Case statement does not handle all possible cases
+{$WARN 6058 off} // Call to subroutine "$1" marked as inline is not inlined
 {$ENDIF}
 
 interface
@@ -75,12 +76,12 @@ Type
     function ParseAttributeSelector: TCSSElement; virtual;
     function ParseWQName: TCSSElement;
     function ParseDeclaration(aIsAt : Boolean = false): TCSSDeclarationElement; virtual;
-    function ParseCall(aName: TCSSString): TCSSElement; virtual;
+    function ParseCall(aName: TCSSString; IsSelector: boolean): TCSSCallElement; virtual;
     procedure ParseSelectorCommaList(aCall: TCSSCallElement); virtual;
     procedure ParseRelationalSelectorCommaList(aCall: TCSSCallElement); virtual;
     procedure ParseNthChildParams(aCall: TCSSCallElement); virtual;
     function ParseUnary: TCSSElement; virtual;
-    function ParseUnit: TCSSUnits; virtual;
+    function ParseUnit: TCSSUnit; virtual;
     function ParseIdentifier : TCSSIdentifierElement; virtual;
     function ParseHashIdentifier : TCSSHashIdentifierElement; virtual;
     function ParseClassName : TCSSClassNameElement; virtual;
@@ -116,7 +117,7 @@ Type
     CSSUnaryElementClass: TCSSUnaryElementClass;
     CSSUnicodeRangeElementClass: TCSSUnicodeRangeElementClass;
     CSSURLElementClass: TCSSURLElementClass;
-    Constructor Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []); overload;
+    Constructor Create(AInput: TStream; ExtraScannerOptions : TCSSScannerOptions = []); overload; // AInput is not freed
     Constructor Create(AScanner : TCSSScanner); virtual; overload;
     Destructor Destroy; override;
     Function Parse : TCSSElement;
@@ -131,6 +132,7 @@ Type
 
 Function TokenToBinaryOperation(aToken : TCSSToken) : TCSSBinaryOperation;
 Function TokenToUnaryOperation(aToken : TCSSToken) : TCSSUnaryOperation;
+Function IsValidCSSAttributeName(const aName: TCSSString): boolean;
 
 implementation
 
@@ -189,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;
@@ -726,28 +748,33 @@ begin
   Result:=FPeekToken;
 end;
 
-function TCSSParser.ParseUnit : TCSSUnits;
+function TCSSParser.ParseUnit : TCSSUnit;
 
+var
+  p: PCSSChar;
+  U: TCSSUnit;
 begin
   Result:=cuNone;
-  if (CurrentToken in [ctkIDENTIFIER,ctkPERCENTAGE]) then
+  case CurrentToken of
+  ctkPERCENTAGE:
     begin
-    Case currentTokenString of
-    '%'   : Result:=cuPERCENT;
-    'px'  : Result:=cuPX;
-    'rem' : Result:=cuREM;
-    'em'  : Result:=cuEM;
-    'fr'  : Result:=cuFR;
-    'vw'  : Result:=cuVW;
-    'vh'  : Result:=cuVH;
-    'pt'  : Result:=cuPT;
-    'deg' : Result:=cuDEG;
-    else
-      // Ignore. For instance margin: 0 auto
+    Result:=cuPercent;
+    Consume(CurrentToken);
     end;
-    if Result<>cuNone then
-      Consume(CurrentToken);
+  ctkIDENTIFIER:
+    begin
+    p:=PCSSChar(CurrentTokenString);
+    for U:=Succ(cuNone) to High(TCSSUnit) do
+      if CompareMem(p,PCSSChar(CSSUnitNames[U]),SizeOf(TCSSChar)*length(CSSUnitNames[U])) then
+        begin
+        Result:=U;
+        Consume(CurrentToken);
+        break;
+        end;
     end;
+  ctkWHITESPACE:
+    Consume(CurrentToken);
+  end;
 end;
 
 function TCSSParser.CreateElement(aClass : TCSSElementClass): TCSSElement;
@@ -797,20 +824,31 @@ end;
 function TCSSParser.ParseInteger: TCSSElement;
 
 Var
-  aValue : Integer;
+  aCode, aValue : Integer;
   aInt : TCSSIntegerElement;
+  OldReturnWhiteSpace: Boolean;
 
 begin
-  aValue:=StrToInt(CurrentTokenString);
+  Val(CurrentTokenString,aValue,aCode);
+  if aCode<>0 then
+    begin
+    DoError(SErrInvalidFloat,[CurrentTokenString]);
+    GetNextToken;
+    exit(nil);
+    end;
   aInt:=TCSSIntegerElement(CreateElement(CSSIntegerElementClass));
+  OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
   try
     aInt.Value:=aValue;
+    Scanner.ReturnWhiteSpace:=true;
     Consume(ctkINTEGER);
     aInt.Units:=ParseUnit;
     Result:=aInt;
     aInt:=nil;
   finally
     aInt.Free;
+    Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
+    SkipWhiteSpace;
   end;
 end;
 
@@ -819,19 +857,29 @@ Var
   aCode : Integer;
   aValue : Double;
   aFloat : TCSSFloatElement;
+  OldReturnWhiteSpace: Boolean;
 
 begin
   Val(CurrentTokenString,aValue,aCode);
   if aCode<>0 then
+    begin
     DoError(SErrInvalidFloat,[CurrentTokenString]);
+    GetNextToken;
+    exit(nil);
+    end;
   aFloat:=TCSSFloatElement(CreateElement(CSSFloatElementClass));
+  OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
   try
-    Consume(ctkFloat);
     aFloat.Value:=aValue;
+    Scanner.ReturnWhiteSpace:=true;
+    Consume(ctkFloat);
     aFloat.Units:=ParseUnit;
+    if CurrentToken=ctkWHITESPACE then
+      GetNextToken;
     Result:=aFloat;
     aFloat:=nil;
   finally
+    Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
     aFloat.Free;
   end;
 end;
@@ -905,6 +953,8 @@ Var
 
 begin
   aDecl:=nil;
+  while CurrentToken=ctkUNKNOWN do
+    GetNextToken;
   if not (CurrentToken in [ctkRBRACE,ctkSEMICOLON]) then
     begin
     aDecl:=ParseDeclaration(aIsAt);
@@ -996,22 +1046,22 @@ function TCSSParser.ParseUnary: TCSSElement;
 var
   Un : TCSSUnaryElement;
   Op : TCSSUnaryOperation;
+  El: TCSSElement;
 
 begin
   Result:=nil;
   if not (CurrentToken in [ctkDOUBLECOLON, ctkMinus, ctkPlus, ctkDiv, ctkGT, ctkTILDE]) then
     Raise ECSSParser.CreateFmt(SUnaryInvalidToken,[CurrentTokenString]);
+  op:=TokenToUnaryOperation(CurrentToken);
+  Consume(CurrentToken);
+  if CurrentToken=ctkWHITESPACE then
+    Raise ECSSParser.CreateFmt(SUnaryInvalidToken,['white space']);
+  El:=ParseComponentValue;
+
   Un:=TCSSUnaryElement(CreateElement(CSSUnaryElementClass));
-  try
-    op:=TokenToUnaryOperation(CurrentToken);
-    Un.Operation:=op;
-    Consume(CurrentToken);
-    Un.Right:=ParseComponentValue;
-    Result:=Un;
-    Un:=nil;
-  finally
-    Un.Free;
-  end;
+  Un.Operation:=op;
+  Un.Right:=El;
+  Result:=Un;
 end;
 
 function TCSSParser.ParseComponentValueList(AllowRules : Boolean = True): TCSSElement;
@@ -1099,7 +1149,16 @@ var
 
 begin
   aToken:=CurrentToken;
+  if aToken=ctkUNKNOWN then
+    begin
+    DoError('invalid');
+    repeat
+      GetNextToken;
+    until CurrentToken<>ctkUNKNOWN;
+    aToken:=CurrentToken;
+    end;
   Case aToken of
+    ctkEOF: exit(nil);
     ctkLPARENTHESIS: Result:=ParseParenthesis;
     ctkURL: Result:=ParseURL;
     ctkPSEUDO: Result:=ParsePseudo;
@@ -1116,13 +1175,12 @@ begin
     ctkINTEGER: Result:=ParseInteger;
     ctkFloat : Result:=ParseFloat;
     ctkPSEUDOFUNCTION,
-    ctkFUNCTION : Result:=ParseCall('');
+    ctkFUNCTION : Result:=ParseCall('',false);
     ctkSTAR: Result:=ParseInvalidToken;
-    ctkIDENTIFIER: Result:=ParseIdentifier;
+    ctkIDENTIFIER,ctkPERCENTAGE: Result:=ParseIdentifier;
     ctkCLASSNAME : Result:=ParseClassName;
   else
     Result:=nil;
-//    Consume(aToken);// continue
   end;
   if aToken in FinalTokens then
     exit;
@@ -1142,7 +1200,7 @@ function TCSSParser.ParseSelector: TCSSElement;
       ctkCLASSNAME : Result:=ParseClassName;
       ctkLBRACKET: Result:=ParseAttributeSelector;
       ctkPSEUDO: Result:=ParsePseudo;
-      ctkPSEUDOFUNCTION: Result:=ParseCall('');
+      ctkPSEUDOFUNCTION: Result:=ParseCall('',true);
     else
       DoWarn(SErrUnexpectedToken ,[
                GetEnumName(TypeInfo(TCSSToken),Ord(CurrentToken)),
@@ -1244,6 +1302,7 @@ begin
       Bin.Free;
       end;
   end;
+  SkipWhiteSpace;
 end;
 
 function TCSSParser.ParseAttributeSelector: TCSSElement;
@@ -1334,14 +1393,15 @@ function TCSSParser.ParseDeclaration(aIsAt: Boolean = false): TCSSDeclarationEle
 Var
   aDecl : TCSSDeclarationElement;
   aKey,aValue : TCSSElement;
-  aPrevDisablePseudo : Boolean;
   aList : TCSSListElement;
+  OldOptions: TCSSScannerOptions;
 
 begin
   aList:=nil;
-  aDecl:= TCSSDeclarationElement(CreateElement(CSSDeclarationElementClass));
+  OldOptions:=Scanner.Options;
+  aDecl:=TCSSDeclarationElement(CreateElement(CSSDeclarationElementClass));
   try
-    aPrevDisablePseudo:= Scanner.DisablePseudo;
+    // read attribute names
     Scanner.DisablePseudo:=True;
     aKey:=ParseComponentValue;
     aDecl.AddKey(aKey);
@@ -1350,7 +1410,7 @@ begin
       While (CurrentToken=ctkCOMMA) do
         begin
         while (CurrentToken=ctkCOMMA) do
-          Consume(ctkCOMMA);
+          GetNextToken;
         aKey:=ParseComponentValue;
         aDecl.AddKey(aKey);
         end;
@@ -1366,12 +1426,13 @@ begin
       if aDecl.Colon then
         Consume(ctkColon)
       end;
-    Scanner.DisablePseudo:=aPrevDisablePseudo;
     aValue:=ParseComponentValue;
     aList:=TCSSListElement(CreateElement(CSSListElementClass));
     aList.AddChild(aValue);
     if aDecl.Colon then
       begin
+      // read attribute value
+      // + and - must be enclosed in whitespace, +3 and -4 are values
       While not (CurrentToken in [ctkEOF,ctkSemicolon,ctkRBRACE,ctkImportant]) do
         begin
         While CurrentToken=ctkCOMMA do
@@ -1395,23 +1456,21 @@ begin
     Result:=aDecl;
     aDecl:=nil;
   finally
-    Scanner.DisablePseudo:=False;
+    Scanner.Options:=OldOptions;
     aDecl.Free;
     aList.Free;
   end;
 end;
 
-function TCSSParser.ParseCall(aName : TCSSString): TCSSElement;
-
+function TCSSParser.ParseCall(aName: TCSSString; IsSelector: boolean
+  ): TCSSCallElement;
 var
   aCall : TCSSCallElement;
   l : Integer;
-  OldReturnWhiteSpace: Boolean;
   aValue: TCSSElement;
 begin
-  OldReturnWhiteSpace:=Scanner.ReturnWhiteSpace;
-  Scanner.ReturnWhiteSpace:=false;
-  aCall:=TCSSCallElement(CreateELement(CSSCallElementClass));
+  if IsSelector then ;
+  aCall:=TCSSCallElement(CreateElement(CSSCallElementClass));
   try
     if (aName='') then
       aName:=CurrentTokenString;
@@ -1419,9 +1478,10 @@ begin
     if (L>0) and (aName[L]='(') then
       aName:=Copy(aName,1,L-1);
     aCall.Name:=aName;
-    if CurrentToken=ctkPSEUDOFUNCTION then
+    if IsSelector and (CurrentToken=ctkPSEUDOFUNCTION) then
       begin
       Consume(ctkPSEUDOFUNCTION);
+      SkipWhiteSpace;
       case aName of
       ':not',':is',':where':
         ParseSelectorCommaList(aCall);
@@ -1431,8 +1491,9 @@ begin
         ParseNthChildParams(aCall);
       end;
       end
-    else
+    else begin
       Consume(ctkFUNCTION);
+    end;
     // Call argument list can be empty: mask()
     While not (CurrentToken in [ctkRPARENTHESIS,ctkEOF]) do
       begin
@@ -1444,7 +1505,7 @@ begin
       end;
       aCall.AddArg(aValue);
       if (CurrentToken=ctkCOMMA) then
-        Consume(ctkCOMMA);
+        GetNextToken;
       end;
     if CurrentToken=ctkEOF then
       DoError(SErrUnexpectedEndOfFile,[aName]);
@@ -1452,7 +1513,6 @@ begin
     Result:=aCall;
     aCall:=nil;
   finally
-    Scanner.ReturnWhiteSpace:=OldReturnWhiteSpace;
     aCall.Free;
   end;
 end;
@@ -1464,12 +1524,12 @@ begin
   while not (CurrentToken in [ctkEOF,ctkRBRACKET,ctkRBRACE,ctkRPARENTHESIS]) do
     begin
     El:=ParseSelector;
-    if EL=nil then exit;
+    if El=nil then exit;
     aCall.AddArg(El);
-    SkipWhiteSpace;
     if CurrentToken<>ctkCOMMA then
       exit;
     GetNextToken;
+    SkipWhiteSpace;
   end;
 end;
 
@@ -1562,12 +1622,12 @@ begin
 
   if CurrentToken in [ctkMINUS,ctkPLUS] then
     aCall.AddArg(ParseUnary);
-  if CurrentToken=ctkINTEGER then
-    aCall.AddArg(ParseInteger);
   if (CurrentToken=ctkIDENTIFIER) and SameText(CurrentTokenString,'of') then
     begin
     aCall.AddArg(ParseIdentifier);
+    SkipWhiteSpace;
     aCall.AddArg(ParseSelector);
+    SkipWhiteSpace;
     end;
 end;
 

文件差异内容过多而无法显示
+ 609 - 264
src/base/fcl-css/fpcssresolver.pas


+ 2609 - 0
src/base/fcl-css/fpcssresparser.pas

@@ -0,0 +1,2609 @@
+{
+    This file is part of the Free Pascal Run time library.
+    Copyright (c) 2023 by Michael Van Canneyt ([email protected])
+
+    This file contains CSS utility class
+
+    See the File COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************
+}
+{$IFNDEF FPC_DOTTEDUNITS}
+unit fpCSSResParser;
+{$ENDIF FPC_DOTTEDUNITS}
+
+{$mode ObjFPC}{$H+}
+{$Interfaces CORBA}
+{$ModeSwitch AdvancedRecords}
+
+{$IF FPC_FULLVERSION>30300}
+  {$WARN 6060 off} // Case statement does not handle all possible cases
+{$ENDIF}
+{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
+interface
+
+{$IFDEF FPC_DOTTEDUNITS}
+uses
+  System.Classes, System.SysUtils, System.Types, System.Contnrs, System.StrUtils,
+  FPCSS.Tree, FPCSS.Scanner, FPCSS.Parser;
+{$ELSE FPC_DOTTEDUNITS}
+uses
+  Classes, SysUtils, Math, Contnrs, AVL_Tree, System.UITypes, fpCSSTree, fpCSSScanner,
+  fpCSSParser;
+{$ENDIF FPC_DOTTEDUNITS}
+
+const
+  CSSIDNone = 0;
+  // built-in attribute IDs
+  CSSAttributeID_ID = 1; // id of attribute key 'id'
+  CSSAttributeID_Class = 2; // id of attribute key 'class'
+  CSSAttributeID_All = 3; // id of attribute key 'all'
+  CSSAttributeID_LastResolver = CSSAttributeID_All;
+
+  // built-in type IDs
+  CSSTypeID_Universal = 1; // id of type '*'
+  CSSTypeID_LastResolver = CSSTypeID_Universal;
+
+  // built-in pseudo class IDs
+  CSSPseudoID_Root = 1; // :root
+  CSSPseudoID_Empty = CSSPseudoID_Root+1; // :empty
+  CSSPseudoID_FirstChild = CSSPseudoID_Empty+1; // :first-child
+  CSSPseudoID_LastChild = CSSPseudoID_FirstChild+1; // :last-child
+  CSSPseudoID_OnlyChild = CSSPseudoID_LastChild+1; // :only-child
+  CSSPseudoID_FirstOfType = CSSPseudoID_OnlyChild+1; // :first-of-type
+  CSSPseudoID_LastOfType = CSSPseudoID_FirstOfType+1; // :last-of-type
+  CSSPseudoID_OnlyOfType = CSSPseudoID_LastOfType+1; // :only-of-type
+  CSSPseudoID_LastResolver = CSSPseudoID_OnlyOfType;
+
+  CSSPseudoClassNames: array[0..CSSPseudoID_LastResolver] of TCSSString = (
+    '?',
+    ':root',
+    ':empty',
+    ':first-child',
+    ':last-child',
+    ':only-child',
+    ':first-of-type',
+    ':last-of-type',
+    ':only-of-type'
+    );
+
+  // built-in pseudo function IDs
+  CSSCallID_Not = 1; // :not()
+  CSSCallID_Is = CSSCallID_Not+1; // :is()
+  CSSCallID_Where = CSSCallID_Is+1; // :where()
+  CSSCallID_Has = CSSCallID_Where+1; // :has()
+  CSSCallID_NthChild = CSSCallID_Has+1; // :nth-child(n)
+  CSSCallID_NthLastChild = CSSCallID_NthChild+1; // :nth-last-child(n)
+  CSSCallID_NthOfType = CSSCallID_NthLastChild+1; // :nth-of-type(n)
+  CSSCallID_NthLastOfType = CSSCallID_NthOfType+1; // :nth-last-of-type(n)
+  CSSCallID_LastResolver = CSSCallID_NthLastOfType;
+
+  CSSSelectorCallNames: array[0..CSSCallID_LastResolver] of TCSSString = (
+    '?',
+    ':not()',
+    ':is()',
+    ':where()',
+    ':has()',
+    ':nth-child(n)',
+    ':nth-last-child(n)',
+    ':nth-of-type(n)',
+    ':nth-last-of-type(n)'
+    );
+
+  // keywords
+  CSSKeywordNone = 1;
+  CSSKeywordInitial = CSSKeywordNone+1;
+  CSSKeywordInherit = CSSKeywordInitial+1;
+  CSSKeywordUnset = CSSKeywordInherit+1;
+  CSSKeywordRevert = CSSKeywordUnset+1;
+  CSSKeywordRevertLayer = CSSKeywordRevert+1;
+  CSSKeywordAuto = CSSKeywordRevertLayer+1;
+  CSSKeyword_LastResolver = CSSKeywordAuto;
+
+  // attribute functions
+  CSSAttrFuncVar = 1;
+
+  CSSMinSafeIntDouble = -$1fffffffffffff; // -9007199254740991 54 bits (52 plus signed bit plus implicit highest bit)
+  CSSMaxSafeIntDouble =  $1fffffffffffff; //  9007199254740991
+
+type
+  TCSSMsgID = int64; // used for message numbers, e.g. error codes
+  TCSSNumericalID = integer; // used for IDs of each type and attribute
+  TCSSNumericalIDArray = array of TCSSNumericalID;
+
+  TCSSNumericalIDKind = (
+    nikAttribute,
+    nikPseudoClass, // e.g. "hover" of ":hover"
+    nikPseudoFunction, // e.g. "is" of ":is()"
+    nikType,
+    nikKeyword,
+    nikAttrFunction // e.g. "calc" of "calc()"
+    );
+  TCSSNumericalIDs = set of TCSSNumericalIDKind;
+
+const
+  nikAllDescriptors = [nikAttribute,nikPseudoClass,nikType]; // all items having a descriptor
+
+  CSSNumericalIDKindNames: array[TCSSNumericalIDKind] of TCSSString = (
+    'Type',
+    'PseudoClass',
+    'PseudoFunction',
+    'Attribute',
+    'Keyword',
+    'AttributeFunction'
+    );
+
+type
+  TCSSAlphaColor = DWord;
+
+  TCSSNamedColor = record
+    Name: TCSSString;
+    Color: TCSSAlphaColor;
+  end;
+const
+  CSSNamedColors: array[0..148] of TCSSNamedColor = (
+    (Name: 'aliceblue'; Color: TCSSAlphaColor($fff0f8ff)),
+    (Name: 'antiquewhite'; Color: TCSSAlphaColor($fffaebd7)),
+    (Name: 'aqua'; Color: TCSSAlphaColor($ff00ffff)),
+    (Name: 'aquamarine'; Color: TCSSAlphaColor($ff7fffd4)),
+    (Name: 'azure'; Color: TCSSAlphaColor($fff0ffff)),
+    (Name: 'beige'; Color: TCSSAlphaColor($fff5f5dc)),
+    (Name: 'bisque'; Color: TCSSAlphaColor($ffffe4c4)),
+    (Name: 'black'; Color: TCSSAlphaColor($ff000000)),
+    (Name: 'blanchedalmond'; Color: TCSSAlphaColor($ffffebcd)),
+    (Name: 'blue'; Color: TCSSAlphaColor($ff0000ff)),
+    (Name: 'blueviolet'; Color: TCSSAlphaColor($ff8a2be2)),
+    (Name: 'brown'; Color: TCSSAlphaColor($ffa52a2a)),
+    (Name: 'burlywood'; Color: TCSSAlphaColor($ffdeb887)),
+    (Name: 'cadetblue'; Color: TCSSAlphaColor($ff5f9ea0)),
+    (Name: 'chartreuse'; Color: TCSSAlphaColor($ff7fff00)),
+    (Name: 'chocolate'; Color: TCSSAlphaColor($ffd2691e)),
+    (Name: 'coral'; Color: TCSSAlphaColor($ffff7f50)),
+    (Name: 'cornflowerblue'; Color: TCSSAlphaColor($ff6495ed)),
+    (Name: 'cornsilk'; Color: TCSSAlphaColor($fffff8dc)),
+    (Name: 'crimson'; Color: TCSSAlphaColor($ffdc143c)),
+    (Name: 'cyan'; Color: TCSSAlphaColor($ff00ffff)),
+    (Name: 'darkblue'; Color: TCSSAlphaColor($ff00008b)),
+    (Name: 'darkcyan'; Color: TCSSAlphaColor($ff008b8b)),
+    (Name: 'darkgoldenrod'; Color: TCSSAlphaColor($ffb8860b)),
+    (Name: 'darkgray'; Color: TCSSAlphaColor($ffa9a9a9)),
+    (Name: 'darkgreen'; Color: TCSSAlphaColor($ff006400)),
+    (Name: 'darkgrey'; Color: TCSSAlphaColor($ffa9a9a9)),
+    (Name: 'darkkhaki'; Color: TCSSAlphaColor($ffbdb76b)),
+    (Name: 'darkmagenta'; Color: TCSSAlphaColor($ff8b008b)),
+    (Name: 'darkolivegreen'; Color: TCSSAlphaColor($ff556b2f)),
+    (Name: 'darkorange'; Color: TCSSAlphaColor($ffff8c00)),
+    (Name: 'darkorchid'; Color: TCSSAlphaColor($ff9932cc)),
+    (Name: 'darkred'; Color: TCSSAlphaColor($ff8b0000)),
+    (Name: 'darksalmon'; Color: TCSSAlphaColor($ffe9967a)),
+    (Name: 'darkseagreen'; Color: TCSSAlphaColor($ff8fbc8f)),
+    (Name: 'darkslateblue'; Color: TCSSAlphaColor($ff483d8b)),
+    (Name: 'darkslategray'; Color: TCSSAlphaColor($ff2f4f4f)),
+    (Name: 'darkslategrey'; Color: TCSSAlphaColor($ff2f4f4f)),
+    (Name: 'darkturquoise'; Color: TCSSAlphaColor($ff00ced1)),
+    (Name: 'darkviolet'; Color: TCSSAlphaColor($ff9400d3)),
+    (Name: 'deeppink'; Color: TCSSAlphaColor($ffff1493)),
+    (Name: 'deepskyblue'; Color: TCSSAlphaColor($ff00bfff)),
+    (Name: 'dimgray'; Color: TCSSAlphaColor($ff696969)),
+    (Name: 'dimgrey'; Color: TCSSAlphaColor($ff696969)),
+    (Name: 'dodgerblue'; Color: TCSSAlphaColor($ff1e90ff)),
+    (Name: 'firebrick'; Color: TCSSAlphaColor($ffb22222)),
+    (Name: 'floralwhite'; Color: TCSSAlphaColor($fffffaf0)),
+    (Name: 'forestgreen'; Color: TCSSAlphaColor($ff228b22)),
+    (Name: 'fuchsia'; Color: TCSSAlphaColor($ffff00ff)),
+    (Name: 'gainsboro'; Color: TCSSAlphaColor($ffdcdcdc)),
+    (Name: 'ghostwhite'; Color: TCSSAlphaColor($fff8f8ff)),
+    (Name: 'gold'; Color: TCSSAlphaColor($ffffd700)),
+    (Name: 'goldenrod'; Color: TCSSAlphaColor($ffdaa520)),
+    (Name: 'gray'; Color: TCSSAlphaColor($ff808080)),
+    (Name: 'green'; Color: TCSSAlphaColor($ff008000)),
+    (Name: 'greenyellow'; Color: TCSSAlphaColor($ffadff2f)),
+    (Name: 'grey'; Color: TCSSAlphaColor($ff808080)),
+    (Name: 'honeydew'; Color: TCSSAlphaColor($fff0fff0)),
+    (Name: 'hotpink'; Color: TCSSAlphaColor($ffff69b4)),
+    (Name: 'indianred'; Color: TCSSAlphaColor($ffcd5c5c)),
+    (Name: 'indigo'; Color: TCSSAlphaColor($ff4b0082)),
+    (Name: 'ivory'; Color: TCSSAlphaColor($fffffff0)),
+    (Name: 'khaki'; Color: TCSSAlphaColor($fff0e68c)),
+    (Name: 'lavender'; Color: TCSSAlphaColor($ffe6e6fa)),
+    (Name: 'lavenderblush'; Color: TCSSAlphaColor($fffff0f5)),
+    (Name: 'lawngreen'; Color: TCSSAlphaColor($ff7cfc00)),
+    (Name: 'lemonchiffon'; Color: TCSSAlphaColor($fffffacd)),
+    (Name: 'lightblue'; Color: TCSSAlphaColor($ffadd8e6)),
+    (Name: 'lightcoral'; Color: TCSSAlphaColor($fff08080)),
+    (Name: 'lightcyan'; Color: TCSSAlphaColor($ffe0ffff)),
+    (Name: 'lightgoldenrodyellow'; Color: TCSSAlphaColor($fffafad2)),
+    (Name: 'lightgray'; Color: TCSSAlphaColor($ffd3d3d3)),
+    (Name: 'lightgreen'; Color: TCSSAlphaColor($ff90ee90)),
+    (Name: 'lightgrey'; Color: TCSSAlphaColor($ffd3d3d3)),
+    (Name: 'lightpink'; Color: TCSSAlphaColor($ffffb6c1)),
+    (Name: 'lightsalmon'; Color: TCSSAlphaColor($ffffa07a)),
+    (Name: 'lightseagreen'; Color: TCSSAlphaColor($ff20b2aa)),
+    (Name: 'lightskyblue'; Color: TCSSAlphaColor($ff87cefa)),
+    (Name: 'lightslategray'; Color: TCSSAlphaColor($ff778899)),
+    (Name: 'lightslategrey'; Color: TCSSAlphaColor($ff778899)),
+    (Name: 'lightsteelblue'; Color: TCSSAlphaColor($ffb0c4de)),
+    (Name: 'lightyellow'; Color: TCSSAlphaColor($ffffffe0)),
+    (Name: 'lime'; Color: TCSSAlphaColor($ff00ff00)),
+    (Name: 'limegreen'; Color: TCSSAlphaColor($ff32cd32)),
+    (Name: 'linen'; Color: TCSSAlphaColor($fffaf0e6)),
+    (Name: 'magenta'; Color: TCSSAlphaColor($ffff00ff)),
+    (Name: 'maroon'; Color: TCSSAlphaColor($ff800000)),
+    (Name: 'mediumaquamarine'; Color: TCSSAlphaColor($ff66cdaa)),
+    (Name: 'mediumblue'; Color: TCSSAlphaColor($ff0000cd)),
+    (Name: 'mediumorchid'; Color: TCSSAlphaColor($ffba55d3)),
+    (Name: 'mediumpurple'; Color: TCSSAlphaColor($ff9370db)),
+    (Name: 'mediumseagreen'; Color: TCSSAlphaColor($ff3cb371)),
+    (Name: 'mediumslateblue'; Color: TCSSAlphaColor($ff7b68ee)),
+    (Name: 'mediumspringgreen'; Color: TCSSAlphaColor($ff00fa9a)),
+    (Name: 'mediumturquoise'; Color: TCSSAlphaColor($ff48d1cc)),
+    (Name: 'mediumvioletred'; Color: TCSSAlphaColor($ffc71585)),
+    (Name: 'midnightblue'; Color: TCSSAlphaColor($ff191970)),
+    (Name: 'mintcream'; Color: TCSSAlphaColor($fff5fffa)),
+    (Name: 'mistyrose'; Color: TCSSAlphaColor($ffffe4e1)),
+    (Name: 'moccasin'; Color: TCSSAlphaColor($ffffe4b5)),
+    (Name: 'navajowhite'; Color: TCSSAlphaColor($ffffdead)),
+    (Name: 'navy'; Color: TCSSAlphaColor($ff000080)),
+    (Name: 'oldlace'; Color: TCSSAlphaColor($fffdf5e6)),
+    (Name: 'olive'; Color: TCSSAlphaColor($ff808000)),
+    (Name: 'olivedrab'; Color: TCSSAlphaColor($ff6b8e23)),
+    (Name: 'orange'; Color: TCSSAlphaColor($ffffa500)),
+    (Name: 'orangered'; Color: TCSSAlphaColor($ffff4500)),
+    (Name: 'orchid'; Color: TCSSAlphaColor($ffda70d6)),
+    (Name: 'palegoldenrod'; Color: TCSSAlphaColor($ffeee8aa)),
+    (Name: 'palegreen'; Color: TCSSAlphaColor($ff98fb98)),
+    (Name: 'paleturquoise'; Color: TCSSAlphaColor($ffafeeee)),
+    (Name: 'palevioletred'; Color: TCSSAlphaColor($ffdb7093)),
+    (Name: 'papayawhip'; Color: TCSSAlphaColor($ffffefd5)),
+    (Name: 'peachpuff'; Color: TCSSAlphaColor($ffffdab9)),
+    (Name: 'peru'; Color: TCSSAlphaColor($ffcd853f)),
+    (Name: 'pink'; Color: TCSSAlphaColor($ffffc0cb)),
+    (Name: 'plum'; Color: TCSSAlphaColor($ffdda0dd)),
+    (Name: 'powderblue'; Color: TCSSAlphaColor($ffb0e0e6)),
+    (Name: 'purple'; Color: TCSSAlphaColor($ff800080)),
+    (Name: 'rebeccapurple'; Color: TCSSAlphaColor($ff663399)),
+    (Name: 'red'; Color: TCSSAlphaColor($ffff0000)),
+    (Name: 'rosybrown'; Color: TCSSAlphaColor($ffbc8f8f)),
+    (Name: 'royalblue'; Color: TCSSAlphaColor($ff4169e1)),
+    (Name: 'saddlebrown'; Color: TCSSAlphaColor($ff8b4513)),
+    (Name: 'salmon'; Color: TCSSAlphaColor($fffa8072)),
+    (Name: 'sandybrown'; Color: TCSSAlphaColor($fff4a460)),
+    (Name: 'seagreen'; Color: TCSSAlphaColor($ff2e8b57)),
+    (Name: 'seashell'; Color: TCSSAlphaColor($fffff5ee)),
+    (Name: 'sienna'; Color: TCSSAlphaColor($ffa0522d)),
+    (Name: 'silver'; Color: TCSSAlphaColor($ffc0c0c0)),
+    (Name: 'skyblue'; Color: TCSSAlphaColor($ff87ceeb)),
+    (Name: 'slateblue'; Color: TCSSAlphaColor($ff6a5acd)),
+    (Name: 'slategray'; Color: TCSSAlphaColor($ff708090)),
+    (Name: 'slategrey'; Color: TCSSAlphaColor($ff708090)),
+    (Name: 'snow'; Color: TCSSAlphaColor($fffffafa)),
+    (Name: 'springgreen'; Color: TCSSAlphaColor($ff00ff7f)),
+    (Name: 'steelblue'; Color: TCSSAlphaColor($ff4682b4)),
+    (Name: 'tan'; Color: TCSSAlphaColor($ffd2b48c)),
+    (Name: 'teal'; Color: TCSSAlphaColor($ff008080)),
+    (Name: 'thistle'; Color: TCSSAlphaColor($ffd8bfd8)),
+    (Name: 'tomato'; Color: TCSSAlphaColor($ffff6347)),
+    (Name: 'transparent'; Color: TCSSAlphaColor($ff0)),
+    (Name: 'turquoise'; Color: TCSSAlphaColor($ff40e0d0)),
+    (Name: 'violet'; Color: TCSSAlphaColor($ffee82ee)),
+    (Name: 'wheat'; Color: TCSSAlphaColor($fff5deb3)),
+    (Name: 'white'; Color: TCSSAlphaColor($ffffffff)),
+    (Name: 'whitesmoke'; Color: TCSSAlphaColor($fff5f5f5)),
+    (Name: 'yellow'; Color: TCSSAlphaColor($ffffff00)),
+    (Name: 'yellowgreen'; Color: TCSSAlphaColor($ff9acd32))
+  );
+
+
+type
+  TCSSRegistryNamedItem = class
+  public
+    Name: TCSSString; // case sensitive
+    Index: TCSSNumericalID;
+  end;
+
+  { TCSSPseudoClassDesc }
+
+  TCSSPseudoClassDesc = class(TCSSRegistryNamedItem)
+  public
+  end;
+  TCSSPseudoClassDescClass = class of TCSSPseudoClassDesc;
+  TCSSPseudoClassDescArray = array of TCSSPseudoClassDesc;
+
+  { TCSSTypeDesc }
+
+  TCSSTypeDesc = class(TCSSRegistryNamedItem)
+  public
+  end;
+  TCSSTypeDescClass = class of TCSSTypeDesc;
+  TCSSTypeDescArray = array of TCSSTypeDesc;
+
+  { TCSSAttributeKeyData }
+
+  TCSSAttributeKeyData = class(TCSSElementOwnedData)
+  public
+    Invalid: boolean;
+    Complete: boolean;
+    Value: TCSSString;
+  end;
+  TCSSAttributeKeyDataClass = class of TCSSAttributeKeyData;
+
+  TCSSBaseResolver = class;
+  TCSSResolverParser = class;
+
+  { TCSSAttributeDesc - general properties of a CSS attribute }
+
+  TCSSAttributeDesc = class(TCSSRegistryNamedItem)
+  public
+    type
+      TCheckEvent = function(Resolver: TCSSBaseResolver): boolean of object;
+      TSplitShorthandEvent = procedure(Resolver: TCSSBaseResolver;
+           var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray) of object;
+  public
+    Syntax: TCSSString; // an optional description
+    Inherits: boolean; // true = the default value is the parent's value
+    All: boolean; // true = can be changed by the 'all' attribute
+    InitialValue: TCSSString;
+    CompProps: array of TCSSAttributeDesc; // if this attribute is a shorthand,
+      // these are the component properties (longhands + sub-shorthands like border-width)
+      // used by the cascade algorithm to delete all overwritten properties
+    OnCheck: TCheckEvent; // called by the parser after reading a declaration and there is no var()
+      // return false if invalid, so the resolver skips this declaration
+    OnSplitShorthand: TSplitShorthandEvent; // called by resolver after resolving var(), if any value is empty, the initialvalue is used
+  end;
+  TCSSAttributeDescClass = class of TCSSAttributeDesc;
+  TCSSAttributeDescArray = array of TCSSAttributeDesc;
+
+  { TCSSRegistry }
+
+  TCSSRegistry = class
+  private
+    FAttrFunctionCount: TCSSNumericalID;
+    FAttributeCount: TCSSNumericalID;
+    FHashLists: array[TCSSNumericalIDKind] of TFPHashList; // name to TCSSRegistryNamedItem
+    FKeywordCount: TCSSNumericalID;
+    FPseudoClassCount: TCSSNumericalID;
+    FPseudoFunctionCount: TCSSNumericalID;
+    FStamp, FModifiedStamp: integer;
+    FTypeCount: TCSSNumericalID;
+    function GetModified: boolean;
+    procedure SetModified(const AValue: boolean);
+  public
+    constructor Create;
+    procedure Init; virtual; // add basic items
+    destructor Destroy; override;
+    function FindNamedItem(Kind: TCSSNumericalIDKind; const aName: TCSSString): TCSSRegistryNamedItem; overload;
+    function IndexOfNamedItem(Kind: TCSSNumericalIDKind; const aName: TCSSString): TCSSNumericalID; overload;
+    procedure ConsistencyCheck; virtual;
+    procedure ChangeStamp;
+    property Stamp: integer read FStamp;
+    property Modified: boolean read GetModified write SetModified;
+  public
+    // attributes
+    Attributes: TCSSAttributeDescArray; // Note: Attributes[0] is nil to spot bugs easily
+    Attribute_ClassOf: TCSSAttributeDescClass;
+    NotAllAttributes: TCSSAttributeDescArray;
+    function AddAttribute(Attr: TCSSAttributeDesc): TCSSAttributeDesc; overload;
+    function AddAttribute(const aName: TCSSString; const aInitialValue: TCSSString = '';
+      aInherits: boolean = false; aAll: boolean = true; aClass: TCSSAttributeDescClass = nil): TCSSAttributeDesc; overload;
+    function FindAttribute(const aName: TCSSString): TCSSAttributeDesc; overload;
+    function IndexOfAttributeName(const aName: TCSSString): TCSSNumericalID; overload;
+    procedure AddSplitLonghand(var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray; AttrID: TCSSNumericalID; const Value: TCSSString); overload;
+    procedure AddSplitLonghandSides(var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray;
+      TopID, RightID, BottomID, LeftID: TCSSNumericalID; const Found: TCSSStringArray); overload;
+    procedure AddSplitLonghandCorners(var AttrIDs: TCSSNumericalIDArray; var Values: TCSSStringArray;
+      TopLeftID, TopRightID, BottomLeftID, BottomRightID: TCSSNumericalID; const Found: TCSSStringArray); overload;
+    property AttributeCount: TCSSNumericalID read FAttributeCount;
+  public
+    // pseudo classes
+    PseudoClasses: TCSSPseudoClassDescArray; // Note: PseudoClasses[0] is nil to spot bugs easily
+    PseudoClass_ClassOf: TCSSPseudoClassDescClass;
+    function AddPseudoClass(aPseudo: TCSSPseudoClassDesc): TCSSPseudoClassDesc; overload;
+    function AddPseudoClass(const aName: TCSSString; aClass: TCSSPseudoClassDescClass = nil): TCSSPseudoClassDesc; overload;
+    function FindPseudoClass(const aName: TCSSString): TCSSPseudoClassDesc; overload;
+    function IndexOfPseudoClassName(const aName: TCSSString): TCSSNumericalID; overload;
+    property PseudoClassCount: TCSSNumericalID read FPseudoClassCount;
+  public
+    // pseudo functions
+    PseudoFunctions: TCSSStringArray;
+    function AddPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
+    function IndexOfPseudoFunction(const aName: TCSSString): TCSSNumericalID; overload;
+    property PseudoFunctionCount: TCSSNumericalID read FPseudoFunctionCount;
+  public
+    // types
+    Types: TCSSTypeDescArray; // Note: Types[0] is nil to spot bugs easily
+    Type_ClassOf: TCSSTypeDescClass;
+    function AddType(aType: TCSSTypeDesc): TCSSTypeDesc; overload;
+    function AddType(const aName: TCSSString; aClass: TCSSTypeDescClass = nil): TCSSTypeDesc; overload;
+    function FindType(const aName: TCSSString): TCSSTypeDesc; overload;
+    function IndexOfTypeName(const aName: TCSSString): TCSSNumericalID; overload;
+    property TypeCount: TCSSNumericalID read FTypeCount;
+  public
+    // keywords
+    Keywords: TCSSStringArray;
+    kwFirstColor, kwLastColor, kwTransparent: TCSSNumericalID;
+    function AddKeyword(const aName: TCSSString): TCSSNumericalID; overload;
+    procedure AddKeywords(const Names: TCSSStringArray; out First, Last: TCSSNumericalID); overload;
+    function IndexOfKeyword(const aName: TCSSString): TCSSNumericalID; overload;
+    procedure AddColorKeywords; virtual;
+    function GetNamedColor(const aName: TCSSString): TCSSAlphaColor; virtual; overload;
+    function GetKeywordColor(KeywordID: TCSSNumericalID): TCSSAlphaColor; virtual; overload;
+    property KeywordCount: TCSSNumericalID read FKeywordCount;
+  public
+    // attribute functions
+    AttrFunctions: TCSSStringArray;
+    afVar: TCSSNumericalID;
+    function AddAttrFunction(const aName: TCSSString): TCSSNumericalID; overload;
+    function IndexOfAttrFunction(const aName: TCSSString): TCSSNumericalID; overload;
+    property AttrFunctionCount: TCSSNumericalID read FAttrFunctionCount;
+  end;
+
+  TCSSValueParserLogEvent = procedure(MsgType: TEventType; const ID: TCSSMsgID;
+                               const Msg: TCSSString; PosEl: TCSSElement) of object;
+
+  { TCSSResolvedIdentifierElement }
+
+  TCSSResolvedIdentifierElement = class(TCSSIdentifierElement)
+  public
+    NumericalID: TCSSNumericalID;
+    Kind: TCSSNumericalIDKind;
+  end;
+
+  { TCSSResolvedPseudoClassElement }
+
+  TCSSResolvedPseudoClassElement = class(TCSSPseudoClassElement)
+  public
+    NumericalID: TCSSNumericalID;
+    Kind: TCSSNumericalIDKind;
+  end;
+
+  { TCSSNthChildParams }
+
+  TCSSNthChildParams = class
+  public
+    Modulo: integer;
+    Start: integer;
+    HasOf: boolean; // for nth-of-type() HasOf=true and OfSelector=nil
+    OfSelector: TCSSElement;
+  end;
+  TCSSNthChildParamsClass = class of TCSSNthChildParams;
+
+  { TCSSResolvedCallElement }
+
+  TCSSResolvedCallElement = class(TCSSCallElement)
+  public
+    NameNumericalID: TCSSNumericalID;
+    Kind: TCSSNumericalIDKind;
+    Params: TObject; // e.g. TCSSNthChildParams
+    destructor Destroy; override;
+  end;
+
+  { TCSSValueData }
+
+  TCSSValueData = class(TCSSElementOwnedData)
+  public
+    NormValue: TCSSString; // normalized value, stripped of comments and e.g. '0.1' instead of '000.100' or '.1'
+  end;
+
+  TCSSResValueKind = (
+    rvkNone,
+    rvkInvalid,
+    rvkSymbol,
+    rvkFloat,
+    rvkCalcFloat,
+    rvkKeyword,
+    rvkKeywordUnknown,
+    rvkFunction,
+    rvkFunctionUnknown,
+    rvkString,
+    rvkHexColor
+    );
+
+  { TCSSResCompValue }
+
+  TCSSResCompValue = record
+    Kind: TCSSResValueKind;
+    StartP, EndP: PCSSChar;
+    function AsString: TCSSString;
+    function FloatAsString: TCSSString;
+    case longint of
+    1: (Float: Double; FloatUnit: TCSSUnit);
+    2: (KeywordID: TCSSNumericalID);
+    3: (FunctionID: TCSSNumericalID; BracketOpen: PCSSChar);
+    4: (Symbol: TCSSToken);
+  end;
+
+  { TCSSCheckAttrParams_Dimension }
+
+  TCSSCheckAttrParams_Dimension = record
+  public
+    AllowedUnits: TCSSUnits;
+    AllowNegative, AllowFrac: boolean;
+    AllowedKeywordIDs: TCSSNumericalIDArray;
+    function Fits(const ResValue: TCSSResCompValue): boolean; overload;
+  end;
+
+  { TCSSBaseResolver }
+
+  TCSSBaseResolver = class(TComponent)
+  private
+    FCSSRegistry: TCSSRegistry;
+  protected
+    procedure SetCSSRegistry(const AValue: TCSSRegistry); virtual;
+  public
+    CurAttrData: TCSSAttributeKeyData;
+    CurDesc: TCSSAttributeDesc;
+    CurValue: TCSSString;
+    CurComp: TCSSResCompValue;
+    function InitParseAttr(Desc: TCSSAttributeDesc; AttrData: TCSSAttributeKeyData; const Value: TCSSString): boolean; virtual; // true if parsing can start
+    // check whole attribute, skipping invalid values, emit warnings:
+    function CheckAttribute_Keyword(const AllowedKeywordIDs: TCSSNumericalIDArray): boolean; virtual;
+    function CheckAttribute_CommaList_Keyword(const AllowedKeywordIDs: TCSSNumericalIDArray): boolean; virtual;
+    function CheckAttribute_Dimension(const Params: TCSSCheckAttrParams_Dimension): boolean; virtual;
+    function CheckAttribute_Color(const AllowedKeywordIDs: TCSSNumericalIDArray): boolean; virtual;
+    // parse whole attribute, skipping invalid values:
+    function ReadNext: boolean;
+    function ReadAttribute_Keyword(out Invalid: boolean; const AllowedKeywordIDs: TCSSNumericalIDArray): boolean; virtual;
+    function ReadAttribute_Dimension(out Invalid: boolean; const Params: TCSSCheckAttrParams_Dimension): boolean; virtual;
+    function ReadAttribute_Color(out Invalid: boolean; const AllowedKeywordIDs: TCSSNumericalIDArray): boolean; virtual;
+    function IsBaseKeyword(KeywordID: TCSSNumericalID): boolean;
+    function IsKeywordIn(aKeywordID: TCSSNumericalID; const KeywordIDs: TCSSNumericalIDArray): boolean; overload;
+    function IsKeywordIn(const KeywordIDs: TCSSNumericalIDArray): boolean; overload;
+    function IsLengthOrPercentage(AllowNegative: boolean): boolean; overload;
+    function IsLengthOrPercentage(const ResValue: TCSSResCompValue; AllowNegative: boolean): boolean; overload;
+    function IsSymbol(Token: TCSSToken): boolean; overload;
+    function GetCompString: TCSSString; overload;
+    function GetCompString(const aValue: string; const ResValue: TCSSResCompValue; EndP: PCSSChar): TCSSString; overload;
+    // low level functions to parse attribute components
+    function ReadComp(var aComp: TCSSResCompValue): boolean; // true if parsing attribute can continue
+    function ReadNumber(var aComp: TCSSResCompValue): boolean;
+    function ReadIdentifier(var aComp: TCSSResCompValue): boolean;
+    procedure SkipToEndOfAttribute(var p: PCSSChar);
+    function SkipString(var p: PCSSChar): boolean;
+    function SkipBrackets(var p: PCSSChar; Lvl: integer = 1): boolean;
+    property CSSRegistry: TCSSRegistry read FCSSRegistry write SetCSSRegistry;
+  end;
+
+  { TCSSResolverParser
+    - resolves identifiers to IDs
+    - warns about constructs unsupported by resolver }
+
+  TCSSResolverParser = class(TCSSParser)
+  private
+    FOnLog: TCSSValueParserLogEvent;
+    FRegistry: TCSSRegistry;
+    FResolver: TCSSBaseResolver;
+  protected
+    function ResolveIdentifier(El: TCSSResolvedIdentifierElement; Kind: TCSSNumericalIDKind): TCSSNumericalID; virtual;
+    function ResolvePseudoClass(El: TCSSResolvedPseudoClassElement): TCSSNumericalID; virtual;
+    function ResolvePseudoFunction(El: TCSSResolvedCallElement): TCSSNumericalID; virtual;
+    function ParseCall(aName: TCSSString; IsSelector: boolean): TCSSCallElement; override;
+    function ParseDeclaration(aIsAt: Boolean): TCSSDeclarationElement; override;
+    function ParseSelector: TCSSElement; override;
+    procedure CheckSelector(El: TCSSElement); virtual;
+    procedure CheckSelectorArray(anArray: TCSSArrayElement); virtual;
+    procedure CheckSelectorArrayBinary(aBinary: TCSSBinaryElement); virtual;
+    procedure CheckSelectorBinary(aBinary: TCSSBinaryElement); virtual;
+    procedure CheckSelectorList(aList: TCSSListElement); virtual;
+    procedure CheckNthChildParams(aCall: TCSSResolvedCallElement); virtual;
+    function ComputeValue(El: TCSSElement): TCSSString; virtual;
+  public
+    CSSNthChildParamsClass: TCSSNthChildParamsClass;
+    CSSAttributeKeyDataClass: TCSSAttributeKeyDataClass;
+    constructor Create(AScanner: TCSSScanner); override; overload;
+    destructor Destroy; override;
+    procedure Log(MsgType: TEventType; const ID: TCSSMsgID; const Msg: TCSSString; PosEl: TCSSElement); virtual;
+    class function IsWhiteSpace(const s: TCSSString): boolean; virtual; overload;
+    property Registry: TCSSRegistry read FRegistry write FRegistry;
+    property Resolver: TCSSBaseResolver read FResolver write FResolver;
+    property OnLog: TCSSValueParserLogEvent read FOnLog write FOnLog;
+  end;
+
+implementation
+
+Const
+  Alpha = ['A'..'Z','a'..'z'];
+  Num   = ['0'..'9'];
+  AlNum = Alpha+Num;
+  AlIden = Alpha+['-'];
+  AlNumIden = AlNum+['-'];
+  Whitespace = [#9,#10,#13,' '];
+  //WhitespaceZ = Whitespace+[#0];
+  Hex = ['0'..'9','a'..'z','A'..'Z'];
+  ValEnd = [#0,#10,#13,#9,' ',';',',','}',')',']']; // used for skipping a component value
+
+{ TCSSRegistry }
+
+procedure TCSSRegistry.SetModified(const AValue: boolean);
+begin
+  if AValue then
+    ChangeStamp
+  else
+    FModifiedStamp:=FStamp;
+end;
+
+function TCSSRegistry.GetModified: boolean;
+begin
+  Result:=FStamp=FModifiedStamp;
+end;
+
+constructor TCSSRegistry.Create;
+var
+  Kind: TCSSNumericalIDKind;
+  i: Integer;
+begin
+  for Kind in TCSSNumericalIDKind do
+    FHashLists[Kind]:=TFPHashList.Create;
+  if Attribute_ClassOf=nil then
+    Attribute_ClassOf:=TCSSAttributeDesc;
+  if PseudoClass_ClassOf=nil then
+    PseudoClass_ClassOf:=TCSSPseudoClassDesc;
+  if Type_ClassOf=nil then
+    Type_ClassOf:=TCSSTypeDesc;
+
+  // init attributes
+  SetLength(Attributes,32);
+  for i:=0 to length(Attributes)-1 do Attributes[i]:=nil;
+  FAttributeCount:=1; // index 0 is CSSIDNone
+
+  // init pseudo classes
+  SetLength(PseudoClasses,32);
+  for i:=0 to length(PseudoClasses)-1 do PseudoClasses[i]:=nil;
+  FPseudoClassCount:=1; // index 0 is CSSIDNone
+
+  // init pseudo functions
+  SetLength(PseudoFunctions,16);
+  FPseudoFunctionCount:=1; // index 0 is CSSIDNone
+
+  // init types
+  SetLength(Types,32);
+  for i:=0 to length(Types)-1 do Types[i]:=nil;
+  FTypeCount:=1; // index 0 is CSSIDNone
+
+  // init keywords
+  SetLength(Keywords,32);
+  FKeywordCount:=1; // index 0 is CSSIDNone
+
+  // init keywords
+  SetLength(AttrFunctions,32);
+  FAttrFunctionCount:=1; // index 0 is CSSIDNone
+end;
+
+procedure TCSSRegistry.Init;
+begin
+  // init attributes
+  if AddAttribute('id').Index<>CSSAttributeID_ID then
+    raise Exception.Create('20240617191749');
+  if AddAttribute('class').Index<>CSSAttributeID_Class then
+    raise Exception.Create('20240617191801');
+  if AddAttribute('all').Index<>CSSAttributeID_All then
+    raise Exception.Create('20240617191816');
+
+  // init pseudo classes
+  if AddPseudoClass('root').Index<>CSSPseudoID_Root then
+    raise Exception.Create('20240623165848');
+  if AddPseudoClass('empty').Index<>CSSPseudoID_Empty then
+    raise Exception.Create('20240623170450');
+  if AddPseudoClass('first-child').Index<>CSSPseudoID_FirstChild then
+    raise Exception.Create('20240623170508');
+  if AddPseudoClass('last-child').Index<>CSSPseudoID_LastChild then
+    raise Exception.Create('20240623170521');
+  if AddPseudoClass('only-child').Index<>CSSPseudoID_OnlyChild then
+    raise Exception.Create('20240623170534');
+  if AddPseudoClass('first-of-type').Index<>CSSPseudoID_FirstOfType then
+    raise Exception.Create('20240623170547');
+  if AddPseudoClass('last-of-type').Index<>CSSPseudoID_LastOfType then
+    raise Exception.Create('20240623170558');
+  if AddPseudoClass('only-of-type').Index<>CSSPseudoID_OnlyOfType then
+    raise Exception.Create('20240623170609');
+
+  // init pseudo functions
+  if AddPseudoFunction('not')<>CSSCallID_Not then
+    raise Exception.Create('20240625183757');
+  if AddPseudoFunction('is')<>CSSCallID_Is then
+    raise Exception.Create('20240625142038');
+  if AddPseudoFunction('where')<>CSSCallID_Where then
+    raise Exception.Create('20240625142049');
+  if AddPseudoFunction('has')<>CSSCallID_Has then
+    raise Exception.Create('20240625142104');
+  if AddPseudoFunction('nth-child')<>CSSCallID_NthChild then
+    raise Exception.Create('20240625142124');
+  if AddPseudoFunction('nth-last-child')<>CSSCallID_NthLastChild then
+    raise Exception.Create('20240625142136');
+  if AddPseudoFunction('nth-of-type')<>CSSCallID_NthOfType then
+    raise Exception.Create('20240625142156');
+  if AddPseudoFunction('nth-last-of-type')<>CSSCallID_NthLastOfType then
+    raise Exception.Create('20240625142212');
+
+  // init types
+  if AddType('*').Index<>CSSTypeID_Universal then
+    raise Exception.Create('20240617190914');
+
+  // init keywords
+  if AddKeyword('none')<>CSSKeywordNone then
+    raise Exception.Create('20240623184021');
+  if AddKeyword('initial')<>CSSKeywordInitial then
+    raise Exception.Create('20240623184030');
+  if AddKeyword('inherit')<>CSSKeywordInherit then
+    raise Exception.Create('20240623184042');
+  if AddKeyword('unset')<>CSSKeywordUnset then
+    raise Exception.Create('20240623184053');
+  if AddKeyword('revert')<>CSSKeywordRevert then
+    raise Exception.Create('20240623184104');
+  if AddKeyword('revert-layer')<>CSSKeywordRevertLayer then
+    raise Exception.Create('20240623184114');
+  if AddKeyword('auto')<>CSSKeywordAuto then
+    raise Exception.Create('20240625182731');
+
+  // init attribute functions
+  if AddAttrFunction('var')<>CSSAttrFuncVar then
+    raise Exception.Create('20240716124054');
+end;
+
+destructor TCSSRegistry.Destroy;
+var
+  i: Integer;
+  Kind: TCSSNumericalIDKind;
+begin
+  for Kind in TCSSNumericalIDKind do
+    FreeAndNil(FHashLists[Kind]);
+
+  // attributes
+  NotAllAttributes:=nil;
+  for i:=1 to AttributeCount-1 do
+    FreeAndNil(Attributes[i]);
+  Attributes:=nil;
+  FAttributeCount:=0;
+
+  // pseudo classes
+  for i:=1 to PseudoClassCount-1 do
+    FreeAndNil(PseudoClasses[i]);
+  PseudoClasses:=nil;
+  FPseudoClassCount:=0;
+
+  // types
+  for i:=1 to TypeCount-1 do
+    FreeAndNil(Types[i]);
+  Types:=nil;
+  FTypeCount:=0;
+
+  // keywords
+  FKeywordCount:=0;
+
+  inherited Destroy;
+end;
+
+function TCSSRegistry.FindNamedItem(Kind: TCSSNumericalIDKind;
+  const aName: TCSSString): TCSSRegistryNamedItem;
+begin
+  if Kind in nikAllDescriptors then
+    Result:=TCSSRegistryNamedItem(FHashLists[Kind].Find(aName))
+  else
+    raise Exception.Create('20240625141820');
+end;
+
+function TCSSRegistry.IndexOfNamedItem(Kind: TCSSNumericalIDKind;
+  const aName: TCSSString): TCSSNumericalID;
+var
+  Item: TCSSRegistryNamedItem;
+  p: Pointer;
+begin
+  if Kind in nikAllDescriptors then
+  begin
+    Item:=TCSSRegistryNamedItem(FHashLists[Kind].Find(aName));
+    if Item<>nil then
+      Result:=Item.Index
+    else
+      Result:=-1;
+  end else begin
+    p:=FHashLists[Kind].Find(aName);
+    if p=nil then
+      exit(CSSIDNone)
+    else
+      Result:={%H-}TCSSNumericalID(p);
+  end;
+end;
+
+procedure TCSSRegistry.ConsistencyCheck;
+var
+  ID, ID2: TCSSNumericalID;
+  AttrDesc, SubAttrDesc: TCSSAttributeDesc;
+  PseudoClassDesc: TCSSPseudoClassDesc;
+  TypeDesc: TCSSTypeDesc;
+  i: Integer;
+  aName: TCSSString;
+begin
+  if AttributeCount>length(Attributes) then
+    raise Exception.Create('20240629102438');
+  for ID:=1 to AttributeCount-1 do
+  begin
+    AttrDesc:=Attributes[ID];
+    if AttrDesc=nil then
+      raise Exception.Create('20240629102530 attr ID='+IntToStr(ID)+' Desc=nil');
+    aName:=AttrDesc.Name;
+    if aName='' then
+      raise Exception.Create('20240629100056 attr ID='+IntToStr(ID)+' missing name');
+    if length(aName)>255 then
+      raise Exception.Create('20240629100143 attr ID='+IntToStr(ID)+' name too long "'+aName+'"');
+    if aName[1]=':' then
+      raise Exception.Create('20240701231211 attr ID='+IntToStr(ID)+' invalid name "'+aName+'"');
+    if AttrDesc.Index<>ID then
+      raise Exception.Create('20240629095849 attr ID='+IntToStr(ID)+' Desc.Index='+IntToStr(AttrDesc.Index)+' "'+aName+'"');
+    ID2:=IndexOfAttributeName(aName);
+    if ID2<>ID then
+      raise Exception.Create('20240629101227 attr ID='+IntToStr(ID)+' "'+aName+'" IndexOf failed: '+IntToStr(ID2));
+
+    if length(AttrDesc.CompProps)>0 then
+    begin
+      // check shorthand
+      for i:=0 to length(AttrDesc.CompProps)-1 do
+      begin
+        SubAttrDesc:=AttrDesc.CompProps[i];
+        if SubAttrDesc=nil then
+          raise Exception.Create('20240629102701 attr ID='+IntToStr(ID)+' Shorthand="'+aName+'" CompDesc=nil '+IntToStr(i));
+        if (SubAttrDesc.Index<=0) then
+          raise Exception.Create('20240629100345 attr ID='+IntToStr(ID)+' Shorthand="'+aName+'" invalid CompAttr '+IntToStr(SubAttrDesc.Index));
+        if (SubAttrDesc.Index>=ID) then
+          raise Exception.Create('20240629100345 attr ID='+IntToStr(ID)+' Shorthand="'+aName+'" CompAttr after Shorthand: SubID='+IntToStr(SubAttrDesc.Index)+' SubName='+SubAttrDesc.Name);
+      end;
+    end;
+  end;
+
+  if PseudoClassCount>length(PseudoClasses) then
+    raise Exception.Create('20240629102438');
+  for ID:=1 to PseudoClassCount-1 do
+  begin
+    PseudoClassDesc:=PseudoClasses[ID];
+    if PseudoClassDesc=nil then
+      raise Exception.Create('20240629102605 pseudo class ID='+IntToStr(ID)+' Desc=nil');
+    aName:=PseudoClassDesc.Name;
+    if aName='' then
+      raise Exception.Create('20240629100652 pseudo class ID='+IntToStr(ID)+' missing name');
+    if length(aName)>255 then
+      raise Exception.Create('20240629100657 pseudo class ID='+IntToStr(ID)+' name too long "'+aName+'"');
+    if aName[1]=':' then
+      raise Exception.Create('20240701231235 pseudo class ID='+IntToStr(ID)+' invalid name "'+aName+'"');
+    if PseudoClassDesc.Index<>ID then
+      raise Exception.Create('20240629100659 pseudo class ID='+IntToStr(ID)+' Desc.Index='+IntToStr(PseudoClassDesc.Index)+' "'+aName+'"');
+    ID2:=IndexOfPseudoClassName(PseudoClassDesc.Name);
+    if ID2<>ID then
+      raise Exception.Create('20240629101227 pseudo class ID='+IntToStr(ID)+' "'+aName+'" IndexOf failed: '+IntToStr(ID2));
+  end;
+
+  if PseudoFunctionCount>length(PseudoFunctions) then
+    raise Exception.Create('20240629103430');
+  for ID:=1 to PseudoFunctionCount-1 do
+  begin
+    aName:=PseudoFunctions[ID];
+    if aName='' then
+      raise Exception.Create('20240629103431 pseudo function ID='+IntToStr(ID)+' missing name');
+    if length(aName)>255 then
+      raise Exception.Create('20240629103433 pseudo function ID='+IntToStr(ID)+' name too long "'+aName+'"');
+    if aName[1]=':' then
+      raise Exception.Create('20240701231235 pseudo function ID='+IntToStr(ID)+' invalid name "'+aName+'"');
+    ID2:=IndexOfPseudoFunction(aName);
+    if ID2<>ID then
+      raise Exception.Create('20240629103434 pseudo function ID='+IntToStr(ID)+' "'+aName+'" IndexOf failed: '+IntToStr(ID2));
+  end;
+
+  if TypeCount>length(Types) then
+    raise Exception.Create('20240629102438');
+  for ID:=1 to TypeCount-1 do
+  begin
+    TypeDesc:=Types[ID];
+    if TypeDesc=nil then
+      raise Exception.Create('20240629102620 type ID='+IntToStr(ID)+' Desc=nil');
+    aName:=TypeDesc.Name;
+    if aName='' then
+      raise Exception.Create('20240629100812 type ID='+IntToStr(ID)+' missing name');
+    if length(aName)>255 then
+      raise Exception.Create('20240629100825 type ID='+IntToStr(ID)+' name too long "'+aName+'"');
+    if aName[1]=':' then
+      raise Exception.Create('20240701231645 type ID='+IntToStr(ID)+' invalid name "'+aName+'"');
+    if TypeDesc.Index<>ID then
+      raise Exception.Create('20240629101013 type ID='+IntToStr(ID)+' Desc.Index='+IntToStr(TypeDesc.Index)+' "'+aName+'"');
+    ID2:=IndexOfTypeName(aName);
+    if ID2<>ID then
+      raise Exception.Create('20240629101529 type ID='+IntToStr(ID)+' "'+aName+'" IndexOf failed: '+IntToStr(ID2));
+  end;
+
+  if KeywordCount>length(Keywords) then
+    raise Exception.Create('20240629103200');
+  for ID:=1 to KeywordCount-1 do
+  begin
+    aName:=Keywords[ID];
+    if aName='' then
+      raise Exception.Create('20240629103223 keyword ID='+IntToStr(ID)+' missing name');
+    if length(aName)>255 then
+      raise Exception.Create('20240629103242 keyword ID='+IntToStr(ID)+' name too long "'+aName+'"');
+    if aName[1]=':' then
+      raise Exception.Create('20240701231656 keyword ID='+IntToStr(ID)+' invalid name "'+aName+'"');
+    ID2:=IndexOfKeyword(aName);
+    if ID2<>ID then
+      raise Exception.Create('20240629103303 keyword ID='+IntToStr(ID)+' "'+aName+'" IndexOf failed: '+IntToStr(ID2));
+  end;
+end;
+
+procedure TCSSRegistry.ChangeStamp;
+begin
+  if FStamp<high(FStamp) then
+    inc(FStamp)
+  else
+    FStamp:=1;
+end;
+
+function TCSSRegistry.AddAttribute(Attr: TCSSAttributeDesc
+  ): TCSSAttributeDesc;
+begin
+  Result:=Attr;
+  if Attr.Name='' then
+    raise ECSSParser.Create('missing name');
+  if FindAttribute(Attr.Name)<>nil then
+    raise ECSSParser.Create('duplicate attribute "'+Attr.Name+'"');
+
+  if AttributeCount=length(Attributes) then
+  begin
+    if AttributeCount<32 then
+      SetLength(Attributes,32)
+    else
+      SetLength(Attributes,2*AttributeCount);
+    FillByte(Attributes[AttributeCount],SizeOf(Pointer)*(length(Attributes)-AttributeCount),0);
+  end;
+  Attributes[AttributeCount]:=Attr;
+  Attr.Index:=AttributeCount;
+  FHashLists[nikAttribute].Add(Attr.Name,Attr);
+  inc(FAttributeCount);
+  ChangeStamp;
+end;
+
+function TCSSRegistry.AddAttribute(const aName: TCSSString;
+  const aInitialValue: TCSSString; aInherits: boolean; aAll: boolean;
+  aClass: TCSSAttributeDescClass): TCSSAttributeDesc;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if FindAttribute(aName)<>nil then
+    raise ECSSParser.Create('duplicate attribute "'+aName+'"');
+  if aClass=nil then
+    aClass:=Attribute_ClassOf;
+
+  Result:=aClass.Create;
+  Result.Name:=aName;
+  Result.InitialValue:=aInitialValue;
+  Result.Inherits:=aInherits;
+  Result.All:=aAll;
+  AddAttribute(Result);
+
+  if not aAll then
+    Insert(Result,NotAllAttributes,length(NotAllAttributes));
+end;
+
+function TCSSRegistry.FindAttribute(const aName: TCSSString
+  ): TCSSAttributeDesc;
+begin
+  Result:=TCSSAttributeDesc(FHashLists[nikAttribute].Find(aName));
+end;
+
+function TCSSRegistry.IndexOfAttributeName(const aName: TCSSString
+  ): TCSSNumericalID;
+var
+  Attr: TCSSAttributeDesc;
+begin
+  Attr:=TCSSAttributeDesc(FHashLists[nikAttribute].Find(aName));
+  if Attr<>nil then
+    Result:=Attr.Index
+  else
+    Result:=-1;
+end;
+
+procedure TCSSRegistry.AddSplitLonghand(var AttrIDs: TCSSNumericalIDArray;
+  var Values: TCSSStringArray; AttrID: TCSSNumericalID; const Value: TCSSString);
+begin
+  System.Insert(AttrID,AttrIDs,length(AttrIDs));
+  System.Insert(Value,Values,length(Values));
+end;
+
+procedure TCSSRegistry.AddSplitLonghandSides(var AttrIDs: TCSSNumericalIDArray;
+  var Values: TCSSStringArray; TopID, RightID, BottomID, LeftID: TCSSNumericalID;
+  const Found: TCSSStringArray);
+begin
+  if length(Found)=0 then
+    exit; // invalid
+
+  Setlength(AttrIDs,4);
+  Setlength(Values,4);
+
+  AttrIDs[0]:=TopID;
+  AttrIDs[1]:=RightID;
+  AttrIDs[2]:=BottomID;
+  AttrIDs[3]:=LeftID;
+
+  // sets sides depending on how many values were passed:
+  // 1: all four the same
+  // 2: top and bottom | left and right
+  // 3: top | left and right | bottom
+  // 4: top | right | bottom | left
+  case length(Found) of
+  1:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[0];
+      Values[2]:=Found[0];
+      Values[3]:=Found[0];
+    end;
+  2:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[0];
+      Values[3]:=Found[1];
+    end;
+  3:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[2];
+      Values[3]:=Found[1];
+    end;
+  4:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[2];
+      Values[3]:=Found[3];
+    end;
+  end;
+end;
+
+procedure TCSSRegistry.AddSplitLonghandCorners(var AttrIDs: TCSSNumericalIDArray;
+  var Values: TCSSStringArray; TopLeftID, TopRightID, BottomLeftID, BottomRightID: TCSSNumericalID;
+  const Found: TCSSStringArray);
+begin
+  if length(Found)=0 then
+    exit; // invalid
+
+  Setlength(AttrIDs,4);
+  Setlength(Values,4);
+
+  AttrIDs[0]:=TopLeftID;
+  AttrIDs[1]:=TopRightID;
+  AttrIDs[2]:=BottomRightID;
+  AttrIDs[3]:=BottomLeftID;
+
+  // sets corners depending on how many values were passed:
+  // 1: all four the same
+  // 2: top-left-and-bottom-right | top-right-and-bottom-left
+  // 3: top-left | top-right-and-bottom-left | bottom-right
+  // 4: top-left | top-right | bottom-right | bottom-left
+
+  case length(Found) of
+  1:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[0];
+      Values[2]:=Found[0];
+      Values[3]:=Found[0];
+    end;
+  2:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[0];
+      Values[3]:=Found[1];
+    end;
+  3:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[2];
+      Values[3]:=Found[1];
+    end;
+  4:
+    begin
+      Values[0]:=Found[0];
+      Values[1]:=Found[1];
+      Values[2]:=Found[2];
+      Values[3]:=Found[3];
+    end;
+  end;
+end;
+
+function TCSSRegistry.AddPseudoClass(aPseudo: TCSSPseudoClassDesc
+  ): TCSSPseudoClassDesc;
+begin
+  Result:=aPseudo;
+  if aPseudo.Name='' then
+    raise ECSSParser.Create('missing name');
+  if FindPseudoClass(aPseudo.Name)<>nil then
+    raise ECSSParser.Create('duplicate pseudo class "'+aPseudo.Name+'"');
+
+  if PseudoClassCount=length(PseudoClasses) then
+  begin
+    if PseudoClassCount<32 then
+      SetLength(PseudoClasses,32)
+    else
+      SetLength(PseudoClasses,2*PseudoClassCount);
+    FillByte(PseudoClasses[PseudoClassCount],SizeOf(Pointer)*(length(PseudoClasses)-PseudoClassCount),0);
+  end;
+  PseudoClasses[PseudoClassCount]:=aPseudo;
+  aPseudo.Index:=PseudoClassCount;
+  FHashLists[nikPseudoClass].Add(aPseudo.Name,aPseudo);
+  inc(FPseudoClassCount);
+  ChangeStamp;
+end;
+
+function TCSSRegistry.AddPseudoClass(const aName: TCSSString;
+  aClass: TCSSPseudoClassDescClass): TCSSPseudoClassDesc;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if FindPseudoClass(aName)<>nil then
+    raise ECSSParser.Create('duplicate pseudo class "'+aName+'"');
+  if aClass=nil then
+    aClass:=PseudoClass_ClassOf;
+
+  Result:=aClass.Create;
+  Result.Name:=aName;
+  AddPseudoClass(Result);
+end;
+
+function TCSSRegistry.FindPseudoClass(const aName: TCSSString
+  ): TCSSPseudoClassDesc;
+begin
+  Result:=TCSSPseudoClassDesc(FHashLists[nikPseudoClass].Find(aName));
+end;
+
+function TCSSRegistry.IndexOfPseudoClassName(const aName: TCSSString
+  ): TCSSNumericalID;
+var
+  aPseudo: TCSSPseudoClassDesc;
+begin
+  aPseudo:=TCSSPseudoClassDesc(FHashLists[nikPseudoClass].Find(aName));
+  if aPseudo<>nil then
+    Result:=aPseudo.Index
+  else
+    Result:=-1;
+end;
+
+function TCSSRegistry.AddPseudoFunction(const aName: TCSSString
+  ): TCSSNumericalID;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if length(aName)>255 then
+    raise ECSSParser.Create('pseudo function name too long');
+  Result:=IndexOfKeyword(aName);
+  if Result>0 then
+    raise ECSSParser.Create('duplicate pseudo function "'+aName+'"');
+
+  if PseudoFunctionCount=length(PseudoFunctions) then
+  begin
+    if PseudoFunctionCount<32 then
+      SetLength(PseudoFunctions,32)
+    else
+      SetLength(PseudoFunctions,2*PseudoFunctionCount);
+  end;
+  Result:=PseudoFunctionCount;
+  PseudoFunctions[Result]:=aName;
+  FHashLists[nikPseudoFunction].Add(aName,{%H-}Pointer(Result));
+  inc(FPseudoFunctionCount);
+  ChangeStamp;
+end;
+
+function TCSSRegistry.IndexOfPseudoFunction(const aName: TCSSString
+  ): TCSSNumericalID;
+var
+  p: Pointer;
+begin
+  p:=FHashLists[nikPseudoFunction].Find(aName);
+  if p=nil then
+    exit(CSSIDNone)
+  else
+    Result:={%H-}TCSSNumericalID(p);
+end;
+
+function TCSSRegistry.AddType(aType: TCSSTypeDesc): TCSSTypeDesc;
+begin
+  Result:=aType;
+  if aType.Name='' then
+    raise ECSSParser.Create('missing name');
+  if FindType(aType.Name)<>nil then
+    raise ECSSParser.Create('duplicate type "'+aType.Name+'"');
+
+  if TypeCount=length(Types) then
+  begin
+    if TypeCount<32 then
+      SetLength(Types,32)
+    else
+      SetLength(Types,2*TypeCount);
+    FillByte(Types[TypeCount],SizeOf(Pointer)*(length(Types)-TypeCount),0);
+  end;
+  Types[TypeCount]:=aType;
+  aType.Index:=TypeCount;
+  FHashLists[nikType].Add(aType.Name,aType);
+  inc(FTypeCount);
+  ChangeStamp;
+end;
+
+function TCSSRegistry.AddType(const aName: TCSSString; aClass: TCSSTypeDescClass
+  ): TCSSTypeDesc;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if FindType(aName)<>nil then
+    raise ECSSParser.Create('duplicate type "'+aName+'"');
+  if aClass=nil then
+    aClass:=Type_ClassOf;
+
+  Result:=aClass.Create;
+  Result.Name:=aName;
+  AddType(Result);
+end;
+
+function TCSSRegistry.FindType(const aName: TCSSString): TCSSTypeDesc;
+begin
+  Result:=TCSSTypeDesc(FHashLists[nikType].Find(aName));
+end;
+
+function TCSSRegistry.IndexOfTypeName(const aName: TCSSString): TCSSNumericalID;
+var
+  aType: TCSSTypeDesc;
+begin
+  aType:=TCSSTypeDesc(FHashLists[nikType].Find(aName));
+  if aType<>nil then
+    Result:=aType.Index
+  else
+    Result:=-1;
+end;
+
+function TCSSRegistry.AddKeyword(const aName: TCSSString): TCSSNumericalID;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if length(aName)>255 then
+    raise ECSSParser.Create('keyword too long');
+  Result:=IndexOfKeyword(aName);
+  if Result>0 then
+    raise ECSSParser.Create('duplicate keyword "'+aName+'"');
+
+  if KeywordCount=length(Keywords) then
+  begin
+    if KeywordCount<32 then
+      SetLength(Keywords,32)
+    else
+      SetLength(Keywords,2*KeywordCount);
+  end;
+  Result:=KeywordCount;
+  Keywords[Result]:=aName;
+  FHashLists[nikKeyword].Add(aName,{%H-}Pointer(Result));
+  inc(FKeywordCount);
+  ChangeStamp;
+end;
+
+procedure TCSSRegistry.AddKeywords(const Names: TCSSStringArray; out First, Last: TCSSNumericalID);
+var
+  i, NewCnt: integer;
+begin
+  if Names=nil then begin
+    First:=CSSIDNone;
+    Last:=CSSIDNone;
+    exit;
+  end;
+  for i:=0 to length(Names)-1 do
+    if IndexOfKeyword(Names[i])>CSSIDNone then
+      raise Exception.Create('20240712215853');
+
+  NewCnt:=KeywordCount+length(Names);
+  if NewCnt>length(Keywords) then
+  begin
+    NewCnt:=(NewCnt div 32 +1) *32;
+    SetLength(Keywords,NewCnt);
+  end;
+
+  First:=KeywordCount;
+  for i:=0 to length(Names)-1 do
+  begin
+    Last:=KeywordCount;
+    Keywords[Last]:=Names[i];
+    FHashLists[nikKeyword].Add(Names[i],{%H-}Pointer(Last));
+    inc(FKeywordCount);
+  end;
+  ChangeStamp;
+end;
+
+function TCSSRegistry.IndexOfKeyword(const aName: TCSSString): TCSSNumericalID;
+var
+  p: Pointer;
+begin
+  p:=FHashLists[nikKeyword].Find(aName);
+  if p=nil then
+    exit(CSSIDNone)
+  else
+    Result:={%H-}TCSSNumericalID(p);
+end;
+
+procedure TCSSRegistry.AddColorKeywords;
+var
+  Names: TCSSStringArray;
+  i: Integer;
+begin
+  SetLength(Names{%H-},length(CSSNamedColors));
+  for i:=0 to High(CSSNamedColors) do
+    Names[i]:=CSSNamedColors[i].Name;
+  AddKeywords(Names,kwFirstColor,kwLastColor);
+  kwTransparent:=IndexOfKeyword('transparent');
+end;
+
+function TCSSRegistry.GetNamedColor(const aName: TCSSString): TCSSAlphaColor;
+begin
+  Result:=GetKeywordColor(IndexOfKeyword(aName));
+end;
+
+function TCSSRegistry.GetKeywordColor(KeywordID: TCSSNumericalID): TCSSAlphaColor;
+begin
+  if (KeywordID<kwFirstColor) or (KeywordID>kwLastColor) then
+    Result:=$ff000000
+  else
+    Result:=CSSNamedColors[KeywordID-kwFirstColor].Color;
+end;
+
+function TCSSRegistry.AddAttrFunction(const aName: TCSSString): TCSSNumericalID;
+begin
+  if aName='' then
+    raise ECSSParser.Create('missing name');
+  if length(aName)>255 then
+    raise ECSSParser.Create('function name too long');
+  Result:=IndexOfAttrFunction(aName);
+  if Result>0 then
+    raise ECSSParser.Create('duplicate attribute function "'+aName+'"');
+
+  if AttrFunctionCount=length(AttrFunctions) then
+  begin
+    if AttrFunctionCount<32 then
+      SetLength(AttrFunctions,32)
+    else
+      SetLength(AttrFunctions,2*AttrFunctionCount);
+  end;
+  Result:=AttrFunctionCount;
+  AttrFunctions[Result]:=aName;
+  FHashLists[nikAttrFunction].Add(aName,{%H-}Pointer(Result));
+  inc(FAttrFunctionCount);
+  ChangeStamp;
+end;
+
+function TCSSRegistry.IndexOfAttrFunction(const aName: TCSSString
+  ): TCSSNumericalID;
+var
+  p: Pointer;
+begin
+  p:=FHashLists[nikAttrFunction].Find(aName);
+  if p=nil then
+    exit(CSSIDNone)
+  else
+    Result:={%H-}TCSSNumericalID(p);
+end;
+
+{ TCSSResolvedCallElement }
+
+destructor TCSSResolvedCallElement.Destroy;
+begin
+  FreeAndNil(Params);
+  inherited Destroy;
+end;
+
+{ TCSSResCompValue }
+
+function TCSSResCompValue.AsString: TCSSString;
+var
+  l: integer;
+begin
+  if (StartP=nil) or (EndP=nil) or (EndP<=StartP) then
+    exit('');
+  l:=EndP-StartP;
+  SetLength(Result,l);
+  Move(StartP^,Result[1],l);
+end;
+
+function TCSSResCompValue.FloatAsString: TCSSString;
+begin
+  Result:=FloatToCSSStr(Float)+CSSUnitNames[FloatUnit];
+end;
+
+{ TCSSCheckAttrParams_Dimension }
+
+function TCSSCheckAttrParams_Dimension.Fits(const ResValue: TCSSResCompValue): boolean;
+var
+  i: Integer;
+begin
+  Result:=false;
+  case ResValue.Kind of
+  rvkFloat:
+    if ResValue.FloatUnit in AllowedUnits then
+    begin
+      if not (ResValue.FloatUnit in AllowedUnits) then exit;
+      if (not AllowNegative) and (ResValue.Float<0) then exit;
+      if (not AllowFrac) and (Frac(ResValue.Float)>0) then exit;
+      exit(true);
+    end else if (ResValue.FloatUnit=cuNone) and (ResValue.Float=0) then
+      exit(true);
+  rvkKeyword:
+    for i:=0 to length(AllowedKeywordIDs)-1 do
+      if ResValue.KeywordID=AllowedKeywordIDs[i] then
+        exit(true);
+  end;
+end;
+
+{ TCSSBaseResolver }
+
+procedure TCSSBaseResolver.SetCSSRegistry(const AValue: TCSSRegistry);
+begin
+  if FCSSRegistry=AValue then Exit;
+  FCSSRegistry:=AValue;
+end;
+
+function TCSSBaseResolver.InitParseAttr(Desc: TCSSAttributeDesc; AttrData: TCSSAttributeKeyData;
+  const Value: TCSSString): boolean;
+var
+  p: PCSSChar;
+begin
+  Result:=false;
+  CurAttrData:=AttrData;
+  CurDesc:=Desc;
+  CurValue:=Value;
+  CurComp:=Default(TCSSResCompValue);
+  CurComp.EndP:=PCSSChar(CurValue);
+  if not ReadNext then
+  begin
+    if CurAttrData<>nil then
+      CurAttrData.Invalid:=true;
+    exit;
+  end;
+  if (CurAttrData<>nil) and (CurComp.Kind=rvkKeyword)
+      and IsBaseKeyword(CurComp.KeywordID) then
+  begin
+    p:=CurComp.EndP;
+    while (p^ in Whitespace) do inc(p);
+    if p^>#0 then
+    begin
+      // "inherit" must be alone
+      CurAttrData.Invalid:=true;
+      exit;
+    end;
+    CurAttrData.Complete:=true;
+  end;
+  Result:=true;
+end;
+
+function TCSSBaseResolver.CheckAttribute_Keyword(const AllowedKeywordIDs: TCSSNumericalIDArray
+  ): boolean;
+begin
+  Result:=ReadAttribute_Keyword(CurAttrData.Invalid,AllowedKeywordIDs);
+end;
+
+function TCSSBaseResolver.CheckAttribute_CommaList_Keyword(
+  const AllowedKeywordIDs: TCSSNumericalIDArray): boolean;
+var
+  i: Integer;
+  Fits: Boolean;
+begin
+  CurAttrData.Invalid:=true;
+  repeat
+    Fits:=false;
+    case CurComp.Kind of
+    rvkKeyword:
+      for i:=0 to length(AllowedKeywordIDs)-1 do
+        if CurComp.KeywordID=AllowedKeywordIDs[i] then
+        begin
+          Fits:=true;
+          break;
+        end;
+    rvkFunction:
+      begin
+        // todo: check for allowed functions
+        Fits:=true;
+      end;
+    end;
+    if not Fits then exit;
+
+    if not ReadNext then
+    begin
+      // ok
+      CurAttrData.Invalid:=false;
+      exit(true);
+    end;
+    if (CurComp.Kind<>rvkSymbol) or (CurComp.Symbol=ctkCOMMA) then
+      exit;
+  until not ReadNext;
+  Result:=false;
+end;
+
+function TCSSBaseResolver.CheckAttribute_Dimension(const Params: TCSSCheckAttrParams_Dimension
+  ): boolean;
+begin
+  Result:=ReadAttribute_Dimension(CurAttrData.Invalid,Params);
+end;
+
+function TCSSBaseResolver.CheckAttribute_Color(const AllowedKeywordIDs: TCSSNumericalIDArray
+  ): boolean;
+begin
+  Result:=ReadAttribute_Color(CurAttrData.Invalid,AllowedKeywordIDs);
+end;
+
+function TCSSBaseResolver.ReadNext: boolean;
+begin
+  Result:=ReadComp(CurComp);
+end;
+
+function TCSSBaseResolver.ReadAttribute_Keyword(out Invalid: boolean;
+  const AllowedKeywordIDs: TCSSNumericalIDArray): boolean;
+var
+  i: Integer;
+begin
+  Invalid:=false;
+  repeat
+    case CurComp.Kind of
+    rvkKeyword:
+      for i:=0 to length(AllowedKeywordIDs)-1 do
+        if CurComp.KeywordID=AllowedKeywordIDs[i] then
+          exit(true);
+    end;
+    // todo: warn if invalid
+  until not ReadNext;
+  Invalid:=true;
+  Result:=false;
+end;
+
+function TCSSBaseResolver.ReadAttribute_Dimension(out Invalid: boolean;
+  const Params: TCSSCheckAttrParams_Dimension): boolean;
+var
+  i: Integer;
+begin
+  Invalid:=true;
+  repeat
+    case CurComp.Kind of
+    rvkFloat:
+      if Params.Fits(CurComp) then
+        exit(true);
+    rvkKeyword:
+      for i:=0 to length(Params.AllowedKeywordIDs)-1 do
+        if CurComp.KeywordID=Params.AllowedKeywordIDs[i] then
+          exit(true);
+    end;
+    // todo: warn if invalid
+  until not ReadNext;
+  Invalid:=true;
+  Result:=false;
+end;
+
+function TCSSBaseResolver.ReadAttribute_Color(out Invalid: boolean;
+  const AllowedKeywordIDs: TCSSNumericalIDArray): boolean;
+var
+  i: Integer;
+begin
+  Invalid:=false;
+  repeat
+    case CurComp.Kind of
+    rvkKeyword:
+      begin
+        if (CurComp.KeywordID>=CSSRegistry.kwFirstColor)
+            and (CurComp.KeywordID<=CSSRegistry.kwLastColor)
+        then
+          exit(true);
+        for i:=0 to length(AllowedKeywordIDs)-1 do
+          if CurComp.KeywordID=AllowedKeywordIDs[i] then
+            exit(true);
+      end;
+    rvkFunction:
+      begin
+        // todo: check for allowed functions
+      end;
+    rvkHexColor:
+      exit(true);
+    end;
+    // todo: warn if invalid
+  until not ReadNext;
+  Invalid:=true;
+  Result:=false;
+end;
+
+function TCSSBaseResolver.ReadComp(var aComp: TCSSResCompValue): boolean;
+var
+  c: TCSSChar;
+  p: PCSSChar;
+  l: SizeInt;
+
+  procedure SetSymbol(s: TCSSToken);
+  begin
+    aComp.Kind:=rvkSymbol;
+    aComp.Symbol:=s;
+    aComp.EndP:=p+1;
+  end;
+
+begin
+  Result:=true;
+  aComp.Kind:=rvkNone;
+
+  p:=aComp.EndP;
+
+  // skip whitespace
+  while (p^ in Whitespace) do inc(p);
+  aComp.StartP:=p;
+  aComp.EndP:=p;
+
+  c:=p^;
+  case c of
+  #0: exit(false);
+  '0'..'9':
+    if ReadNumber(aComp) then exit;
+  ',':
+    begin
+      SetSymbol(ctkCOMMA);
+      exit;
+    end;
+  ')':
+    begin
+      SetSymbol(ctkRPARENTHESIS);
+      exit;
+    end;
+  '+':
+    case p[1] of
+    '0'..'9','.':
+      if ReadNumber(aComp) then exit;
+    #0,#9,#10,#13,' ':
+      begin
+        SetSymbol(ctkPLUS);
+        exit;
+      end;
+    end;
+  '-':
+    case p[1] of
+    '0'..'9','.':
+      if ReadNumber(aComp) then exit;
+    'a'..'z','A'..'Z','-':
+      if ReadIdentifier(aComp) then exit;
+    #0,#9,#10,#13,' ':
+      begin
+        SetSymbol(ctkMINUS);
+        exit;
+      end;
+    end;
+  '.':
+    case p[1] of
+    '0'..'9':
+      if ReadNumber(aComp) then exit;
+    else
+      SetSymbol(ctkDOT);
+      exit;
+    end;
+  '*':
+    begin
+      if p[1]='=' then
+      begin
+        inc(p);
+        SetSymbol(ctkSTAREQUAL);
+      end else
+        SetSymbol(ctkSTAR);
+      exit;
+    end;
+  '/':
+    begin
+      SetSymbol(ctkDIV);
+      exit;
+    end;
+  'a'..'z','A'..'Z':
+    if ReadIdentifier(aComp) then exit;
+  '#':
+    begin
+      inc(p);
+      while p^ in Hex do inc(p);
+      l:=p-aComp.StartP;
+      case l of
+      4,5,7,9:
+        begin
+          // #rgb, #rgba, #rrggbb, #rrggbbaa
+          aComp.Kind:=rvkHexColor;
+          aComp.EndP:=p;
+          exit;
+        end;
+      end;
+    end;
+  end;
+
+  // skip unknown aComp
+  aComp.Kind:=rvkInvalid;
+  repeat
+    if p^ in ValEnd then break;
+    case p^ of
+    '(','[': SkipBrackets(p);
+    '''','"': SkipString(p);
+    else inc(p);
+    end;
+  until false;
+  aComp.EndP:=p;
+end;
+
+function TCSSBaseResolver.ReadNumber(var aComp: TCSSResCompValue): boolean;
+var
+  Negative, HasNumber: Boolean;
+  Divisor: double;
+  Exponent: Integer;
+  d: Float;
+  U: TCSSUnit;
+  StartP, p: PCSSChar;
+begin
+  Result:=false;
+  aComp.Kind:=rvkInvalid;
+  p:=aComp.StartP;
+
+  // number: 1, 0.2, .3, 4.01, 0.0, +0.0, -0.0, .50, 2e3, -6.7E-2
+  if p^='-' then
+  begin
+    Negative:=true;
+    inc(p);
+  end else begin
+    Negative:=false;
+    if p^='+' then
+      inc(p);
+  end;
+  HasNumber:=false;
+  aComp.Float:=0;
+  if p^ in Num then
+  begin
+    // read significand
+    HasNumber:=true;
+    repeat
+      aComp.Float:=aComp.Float*10+ord(p^)-ord('0');
+      if aComp.Float>CSSMaxSafeIntDouble then
+        exit; // loosing precision
+      inc(p);
+    until not (p^ in Num);
+  end;
+  if p^='.' then
+  begin
+    // read fraction
+    inc(p);
+    if not (p^ in Num) then exit;
+    Divisor:=1;
+    repeat
+      Divisor:=Divisor*10;
+      aComp.Float:=aComp.Float*10+ord(p^)-ord('0');
+      if (Divisor>CSSMaxSafeIntDouble)
+          or (aComp.Float>CSSMaxSafeIntDouble) then
+        exit; // loosing precision
+      inc(p);
+    until not (p^ in Num);
+    aComp.Float:=aComp.Float/Divisor;
+  end else if not HasNumber then
+    exit;
+  if Negative then
+    aComp.Float:=-aComp.Float;
+
+  if (p^ in ['e','E']) and not (p[1] in ['a'..'z']) then
+  begin
+    inc(p);
+    if p^='-' then
+    begin
+      Negative:=true;
+      inc(p);
+    end else begin
+      Negative:=false;
+      if p^='+' then
+        inc(p);
+    end;
+    if not (p^ in Num) then exit;
+    Exponent:=0;
+    repeat
+      inc(p);
+      Exponent:=Exponent*10+ord(p^)-ord('0');
+      if Exponent>2047 then
+        exit; // out of bounds
+    until not (p^ in Num);
+    if Exponent>0 then
+    begin
+      if Negative then
+        Exponent:=-Exponent;
+      try
+        d:=Power(10,Exponent);
+        aComp.Float:=aComp.Float*d;
+      except
+        exit;
+      end;
+    end;
+  end;
+  aComp.Kind:=rvkFloat;
+
+  // parse unit
+  U:=cuNone;
+  case p^ of
+  '%':
+    begin
+      inc(p);
+      U:=cuPercent;
+    end;
+  'a'..'z','A'..'Z':
+    begin
+      StartP:=p;
+      while p^ in Alpha do inc(p);
+      U:=high(TCSSUnit);
+      while (U>cuNone) and not CompareMem(StartP,PChar(CSSUnitNames[U]),length(CSSUnitNames[U])) do
+        U:=pred(U);
+      if U=cuNone then
+        exit; // unknown unit
+    end;
+  end;
+  aComp.FloatUnit:=U;
+  aComp.EndP:=p;
+
+  Result:=true;
+  //writeln('TCSSBaseResolver.ReadNumber "',p,'" Value=',FloatToCSSStr(aComp.Float),' U=',U,' Kind=',aComp.Kind,' Result=',Result);
+end;
+
+function TCSSBaseResolver.ReadIdentifier(var aComp: TCSSResCompValue): boolean;
+var
+  Identifier: TCSSString;
+  IsFunc: Boolean;
+  p: PCSSChar;
+begin
+  Result:=false;
+  aComp.Kind:=rvkInvalid;
+  p:=aComp.EndP;
+  if not (p^ in AlIden) then exit;
+  repeat
+    inc(p);
+  until not (p^ in AlNumIden);
+  SetString(Identifier,aComp.StartP,p-aComp.StartP);
+  IsFunc:=p^='(';
+  if IsFunc then
+  begin
+    // function call
+    aComp.BracketOpen:=p;
+    if not SkipBrackets(p) then
+    begin
+      aComp.EndP:=p;
+      exit;
+    end;
+  end;
+  aComp.EndP:=p;
+
+  if IsFunc then
+  begin
+    aComp.FunctionID:=CSSRegistry.IndexOfAttrFunction(Identifier);
+    if aComp.FunctionID>CSSIDNone then
+      aComp.Kind:=rvkFunction
+    else
+      aComp.Kind:=rvkFunctionUnknown;
+  end else begin
+    aComp.KeywordID:=CSSRegistry.IndexOfKeyword(Identifier);
+    if aComp.KeywordID>CSSIDNone then
+      aComp.Kind:=rvkKeyword
+    else
+      aComp.Kind:=rvkKeywordUnknown;
+  end;
+  Result:=true;
+end;
+
+function TCSSBaseResolver.IsBaseKeyword(KeywordID: TCSSNumericalID): boolean;
+begin
+  Result:=(KeywordID>=CSSKeywordInitial) and (KeywordID<=CSSKeywordRevertLayer);
+end;
+
+function TCSSBaseResolver.IsKeywordIn(aKeywordID: TCSSNumericalID;
+  const KeywordIDs: TCSSNumericalIDArray): boolean;
+var
+  i: Integer;
+begin
+  for i:=0 to length(KeywordIDs)-1 do
+    if KeywordIDs[i]=aKeywordID then
+      exit(true);
+  Result:=false;
+end;
+
+function TCSSBaseResolver.IsKeywordIn(const KeywordIDs: TCSSNumericalIDArray): boolean;
+var
+  aKeywordID: TCSSNumericalID;
+  i: Integer;
+begin
+  Result:=false;
+  if CurComp.Kind<>rvkKeyword then exit;
+  aKeywordID:=CurComp.KeywordID;
+  for i:=0 to length(KeywordIDs)-1 do
+    if KeywordIDs[i]=aKeywordID then
+      exit(true);
+end;
+
+function TCSSBaseResolver.IsLengthOrPercentage(AllowNegative: boolean): boolean;
+begin
+  Result:=false;
+  case CurComp.Kind of
+  rvkFloat:
+    if CurComp.FloatUnit in cuAllLengthsAndPercent then
+    begin
+      if (not AllowNegative) and (CurComp.Float<0) then exit;
+      exit(true);
+    end
+    else if (CurComp.FloatUnit=cuNone) and (CurComp.Float=0) then
+      exit(true); // 0 without unit is allowed
+  end;
+end;
+
+function TCSSBaseResolver.IsLengthOrPercentage(const ResValue: TCSSResCompValue;
+  AllowNegative: boolean): boolean;
+begin
+  Result:=false;
+  case ResValue.Kind of
+  rvkFloat:
+    if ResValue.FloatUnit in cuAllLengthsAndPercent then
+    begin
+      if (not AllowNegative) and (ResValue.Float<0) then exit;
+      exit(true);
+    end
+    else if (ResValue.FloatUnit=cuNone) and (ResValue.Float=0) then
+      exit(true);
+  end;
+end;
+
+function TCSSBaseResolver.IsSymbol(Token: TCSSToken): boolean;
+begin
+  Result:=(CurComp.Kind=rvkSymbol) and (CurComp.Symbol=Token);
+end;
+
+function TCSSBaseResolver.GetCompString: TCSSString;
+var
+  StartP: PCSSChar;
+begin
+  if CurComp.Kind=rvkKeyword then
+    exit(CSSRegistry.Keywords[CurComp.KeywordID]);
+  StartP:=CurComp.StartP;
+  if (StartP=PCSSChar(CurValue)) and (CurComp.EndP-StartP = length(CurValue)) then
+    Result:=CurValue
+  else
+    SetString(Result,StartP,CurComp.EndP-StartP);
+end;
+
+function TCSSBaseResolver.GetCompString(const aValue: string; const ResValue: TCSSResCompValue;
+  EndP: PCSSChar): TCSSString;
+var
+  Start: PCSSChar;
+begin
+  if ResValue.Kind=rvkKeyword then
+    exit(CSSRegistry.Keywords[ResValue.KeywordID]);
+  Start:=ResValue.StartP;
+  if (Start=PCSSChar(aValue)) and (EndP-Start = length(aValue)) then
+    Result:=aValue
+  else
+    SetString(Result,Start,EndP-Start);
+end;
+
+procedure TCSSBaseResolver.SkipToEndOfAttribute(var p: PCSSChar);
+begin
+  repeat
+    case p^ of
+    #0,'{','}',';': exit;
+    '''','"': SkipString(p);
+    else inc(p);
+    end;
+  until false;
+end;
+
+function TCSSBaseResolver.SkipString(var p: PCSSChar): boolean;
+var
+  Delim, c: TCSSChar;
+begin
+  Result:=false;
+  Delim:=p^;
+  repeat
+    inc(p);
+    c:=p^;
+    if c=Delim then
+    begin
+      inc(p);
+      exit(true);
+    end else if c=#0 then
+      exit
+    else
+      inc(p);
+  until false;
+end;
+
+function TCSSBaseResolver.SkipBrackets(var p: PCSSChar; Lvl: integer): boolean;
+const
+  CSSMaxBracketLvl = 10;
+var
+  CloseBracket: TCSSChar;
+begin
+  Result:=false;
+  if Lvl>CSSMaxBracketLvl then
+  begin
+    SkipToEndOfAttribute(p);
+    exit;
+  end;
+
+  if p^='[' then
+    CloseBracket:=']'
+  else
+    CloseBracket:=')';
+  repeat
+    inc(p);
+    case p^ of
+    #0,'{','}',';': exit;
+    '''','"': SkipString(p);
+    '(','[': SkipBrackets(p,Lvl+1);
+    ')',']':
+      if p^=CloseBracket then
+      begin
+        inc(p);
+        exit(true);
+      end else begin
+        SkipToEndOfAttribute(p);
+        exit;
+      end;
+    end;
+  until false;
+end;
+
+{ TCSSResolverParser }
+
+function TCSSResolverParser.ResolveIdentifier(El: TCSSResolvedIdentifierElement;
+  Kind: TCSSNumericalIDKind): TCSSNumericalID;
+var
+  aName: TCSSString;
+begin
+  if El.NumericalID<>CSSIDNone then
+    raise Exception.Create('20240701143234');
+  aName:=El.Name;
+  if Kind=nikPseudoClass then
+  begin
+    // pseudo classes are ASCII case insensitive
+    System.Delete(aName,1,1);
+    aName:=lowercase(aName);
+  end;
+
+  El.Kind:=Kind;
+  Result:=Registry.IndexOfNamedItem(Kind,aName);
+  //writeln('TCSSResolverParser.ResolveIdentifier ',aName,' ID=',Result);
+  if Result=CSSIDNone then
+  begin
+    El.NumericalID:=-1;
+    Log(etWarning,20240625130648,'unknown '+CSSNumericalIDKindNames[Kind]+' "'+aName+'"',El);
+  end else
+    El.NumericalID:=Result;
+end;
+
+function TCSSResolverParser.ResolvePseudoClass(
+  El: TCSSResolvedPseudoClassElement): TCSSNumericalID;
+var
+  aName: TCSSString;
+begin
+  aName:=El.Name;
+  // pseudo classes are ASCII case insensitive
+  System.Delete(aName,1,1);
+  aName:=lowercase(aName);
+
+  if El.NumericalID<>CSSIDNone then
+    raise Exception.Create('20240701143234');
+
+  El.Kind:=nikPseudoClass;
+  Result:=Registry.IndexOfNamedItem(nikPseudoClass,aName);
+  //writeln('TCSSResolverParser.ResolvePseudoClass ',aName,' ID=',Result);
+  if Result<=CSSIDNone then
+  begin
+    El.NumericalID:=-1;
+    Log(etWarning,20240625130648,'unknown pseudo class "'+aName+'"',El);
+  end else
+    El.NumericalID:=Result;
+end;
+
+function TCSSResolverParser.ResolvePseudoFunction(El: TCSSResolvedCallElement
+  ): TCSSNumericalID;
+var
+  aName: TCSSString;
+begin
+  if El.NameNumericalID<>CSSIDNone then
+    raise Exception.Create('20240701143035');
+  aName:=El.Name;
+  if aName[1]<>':' then
+    raise Exception.Create('20240701143650');
+
+  // pseudo functions are ASCII case insensitive
+  System.Delete(aName,1,1);
+  aName:=lowercase(aName);
+
+  El.Kind:=nikPseudoFunction;
+  Result:=Registry.IndexOfNamedItem(nikPseudoFunction,aName);
+  //writeln('TCSSResolverParser.ResolvePseudoFunction ',aName,' ID=',Result);
+  if Result=CSSIDNone then
+  begin
+    El.NameNumericalID:=-1;
+    Log(etWarning,20240625130648,'unknown pseudo class "'+aName+'"',El);
+  end else
+    El.NameNumericalID:=Result;
+end;
+
+function TCSSResolverParser.ParseCall(aName: TCSSString; IsSelector: boolean
+  ): TCSSCallElement;
+var
+  CallID: TCSSNumericalID;
+begin
+  Result:=inherited ParseCall(aName, IsSelector);
+  if IsSelector then
+  begin
+    if Result.Name[1]=':' then
+    begin
+      CallID:=ResolvePseudoFunction(TCSSResolvedCallElement(Result));
+      case CallID of
+      CSSCallID_NthChild,CSSCallID_NthLastChild,
+      CSSCallID_NthOfType,CSSCallID_NthLastOfType: CheckNthChildParams(TCSSResolvedCallElement(Result));
+      end;
+    end
+    else
+      Log(etWarning,20240701222744,'invalid selector function',Result);
+  end;
+end;
+
+function TCSSResolverParser.ParseDeclaration(aIsAt: Boolean
+  ): TCSSDeclarationElement;
+var
+  aKey: TCSSElement;
+  AttrId: TCSSNumericalID;
+  Desc: TCSSAttributeDesc;
+  AttrData: TCSSAttributeKeyData;
+  i, ChildCnt: Integer;
+begin
+  Result:=inherited ParseDeclaration(aIsAt);
+  if Result.KeyCount<>1 then
+  begin
+    if Result.KeyCount<1 then
+      Log(etWarning,20231112135955,'missing keys in declaration',Result);
+    if Result.KeyCount>1 then
+      Log(etWarning,20231112140722,'too many keys in declaration',Result);
+    exit;
+  end;
+
+  aKey:=Result.Keys[0];
+  if aKey is TCSSResolvedIdentifierElement then
+  begin
+    // todo: custom attributes
+    AttrId:=ResolveIdentifier(TCSSResolvedIdentifierElement(aKey),nikAttribute);
+
+    if aKey.CustomData<>nil then
+      raise Exception.Create('20240626113536');
+    AttrData:=CSSAttributeKeyDataClass.Create;
+    aKey.CustomData:=AttrData;
+
+    ChildCnt:=Result.ChildCount;
+    if ChildCnt=0 then
+    begin
+      AttrData.Invalid:=true;
+      exit;
+    end;
+    for i:=0 to ChildCnt-1 do
+    begin
+      if (i>0) then
+        AttrData.Value+=', ';
+      AttrData.Value+=Result.Children[i].AsString;
+    end;
+
+    if AttrId>=CSSAttributeID_All then
+    begin
+      Desc:=Registry.Attributes[AttrId];
+
+      if Pos('var(',AttrData.Value)>0 then
+      begin
+        // cannot be parsed yet
+      end else if Resolver.InitParseAttr(Desc,AttrData,AttrData.Value) then
+      begin
+        if Assigned(Desc.OnCheck) then
+          AttrData.Invalid:=not Desc.OnCheck(Resolver);
+      end;
+      {$IFDEF VerboseCSSResolver}
+      if AttrData.Invalid then
+        Log(etWarning,20240710162400,'Invalid CSS attribute value: '+Result.AsString,aKey);
+      {$ENDIF}
+    end;
+  end else begin
+    Log(etWarning,20220908230855,'Expected property name, but found '+aKey.ClassName,aKey);
+  end;
+end;
+
+function TCSSResolverParser.ParseSelector: TCSSElement;
+begin
+  Result:=inherited ParseSelector;
+  CheckSelector(Result);
+end;
+
+procedure TCSSResolverParser.CheckSelector(El: TCSSElement);
+var
+  C: TClass;
+begin
+  C:=El.ClassType;
+  if C=TCSSResolvedIdentifierElement then
+    // e.g. div {}
+    ResolveIdentifier(TCSSResolvedIdentifierElement(El),nikType)
+  else if C=TCSSHashIdentifierElement then
+    // e.g. #id {}
+  else if C=TCSSClassNameElement then
+    // e.g. .classname {}
+  else if C=TCSSResolvedPseudoClassElement then
+    // e.g. :pseudoclass {}
+    ResolvePseudoClass(TCSSResolvedPseudoClassElement(El))
+  else if C=TCSSBinaryElement then
+    CheckSelectorBinary(TCSSBinaryElement(El))
+  else if C=TCSSArrayElement then
+    CheckSelectorArray(TCSSArrayElement(El))
+  else if C=TCSSListElement then
+    CheckSelectorList(TCSSListElement(El))
+  else if C=TCSSResolvedCallElement then
+    // checked via ParseCall
+  else
+    Log(etWarning,20240625131810,'Unknown CSS selector element',El);
+end;
+
+procedure TCSSResolverParser.CheckSelectorArray(anArray: TCSSArrayElement);
+var
+  {$IFDEF VerboseCSSResolver}
+  i: integer;
+  {$ENDIF}
+  El: TCSSElement;
+  C: TClass;
+  aValue: TCSSString;
+begin
+  if anArray.Prefix<>nil then
+  begin
+    Log(etWarning,20240625134737,'Invalid CSS attribute selector prefix',anArray.Prefix);
+    exit;
+  end;
+
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolverParser.CheckSelectorArray Prefix=',GetCSSObj(anArray.Prefix),' ChildCount=',anArray.ChildCount);
+  for i:=0 to anArray.ChildCount-1 do
+    writeln('TCSSResolverParser.CheckSelectorArray ',i,' ',GetCSSObj(anArray.Children[i]));
+  {$ENDIF}
+  if anArray.ChildCount<1 then
+  begin
+    Log(etWarning,20220910154033,'Invalid CSS attribute selector',anArray);
+    exit;
+  end;
+
+  if anArray.ChildCount>1 then
+  begin
+    El:=anArray.Children[1];
+    C:=El.ClassType;
+    if C=TCSSResolvedIdentifierElement then
+    begin
+      aValue:=TCSSResolvedIdentifierElement(El).Value;
+      case aValue of
+      'i','s': ;
+      else
+        Log(etWarning,20240625134931,'Invalid attribute modifier "'+aValue+'"',El);
+        exit;
+      end;
+    end else begin
+      Log(etWarning,20240625134940,'Invalid CSS attribute modifier',El);
+      exit;
+    end;
+  end;
+  if (anArray.ChildCount>2) then
+    Log(etWarning,20240625134951,'Invalid CSS attribute modifier',anArray.Children[2]);
+
+  El:=anArray.Children[0];
+  C:=El.ClassType;
+  if C=TCSSResolvedIdentifierElement then
+  begin
+    // [name]  ->  has explicit attribute
+    ResolveIdentifier(TCSSResolvedIdentifierElement(El),nikAttribute);
+  end else if C=TCSSBinaryElement then
+    CheckSelectorArrayBinary(TCSSBinaryElement(El))
+  else begin
+    Log(etWarning,20240625135119,'Invalid CSS array selector',El);
+  end;
+end;
+
+procedure TCSSResolverParser.CheckSelectorArrayBinary(aBinary: TCSSBinaryElement
+  );
+var
+  Left, Right: TCSSElement;
+  C: TClass;
+begin
+  Left:=aBinary.Left;
+  if Left.ClassType<>TCSSResolvedIdentifierElement then
+  begin
+    Log(etWarning,20240625154314,'Invalid CSS array selector, expected attribute',Left);
+    exit;
+  end;
+  ResolveIdentifier(TCSSResolvedIdentifierElement(Left),nikAttribute);
+
+  Right:=aBinary.Right;
+  C:=Right.ClassType;
+  if (C=TCSSStringElement) or (C=TCSSIntegerElement) or (C=TCSSFloatElement)
+      or (C=TCSSResolvedIdentifierElement) then
+    // ok
+  else begin
+    Log(etWarning,20240625154455,'Invalid CSS array selector, expected string',Right);
+    exit;
+  end;
+  ComputeValue(Right);
+
+  case aBinary.Operation of
+  boEquals,
+  boSquaredEqual,
+  boDollarEqual,
+  boPipeEqual,
+  boStarEqual,
+  boTildeEqual: ;
+  else
+    Log(etWarning,20240625154617,'Invalid CSS array selector operator',aBinary);
+  end;
+end;
+
+procedure TCSSResolverParser.CheckSelectorBinary(aBinary: TCSSBinaryElement);
+begin
+  case aBinary.Operation of
+  boGT,
+  boPlus,
+  boTilde,
+  boWhiteSpace: ;
+  else
+    Log(etWarning,20240625153307,'Invalid CSS binary selector '+BinaryOperators[aBinary.Operation],aBinary);
+  end;
+
+  CheckSelector(aBinary.Left);
+  CheckSelector(aBinary.Right);
+end;
+
+procedure TCSSResolverParser.CheckSelectorList(aList: TCSSListElement);
+var
+  i: Integer;
+  El: TCSSElement;
+begin
+  for i:=0 to aList.ChildCount-1 do
+  begin
+    El:=aList.Children[i];
+    {$IFDEF VerboseCSSResolver}
+    writeln('TCSSResolverParser.CheckSelectorList ',i,' ',GetCSSObj(El),' AsString=',El.AsString);
+    {$ENDIF}
+    CheckSelector(El);
+  end;
+end;
+
+procedure TCSSResolverParser.CheckNthChildParams(aCall: TCSSResolvedCallElement);
+
+  procedure NthWarn(const ID: TCSSMsgID; const Msg: string; PosEl: TCSSElement);
+  begin
+    Log(etWarning,ID,CSSSelectorCallNames[aCall.NameNumericalID]+' '+Msg,PosEl);
+  end;
+
+var
+  i, ArgCount, aModulo, aStart: Integer;
+  Arg, OffsetEl: TCSSElement;
+  Str: TCSSString;
+  UnaryEl, anUnary: TCSSUnaryElement;
+  Params: TCSSNthChildParams;
+begin
+  if aCall.Params<>nil then
+    raise Exception.Create('20240625150639');
+  ArgCount:=aCall.ArgCount;
+  {$IFDEF VerboseCSSResolver}
+  writeln('TCSSResolverParser.CheckSelectorCall_NthChild ArgCount=',aCall.ArgCount);
+  for i:=0 to aCall.ArgCount-1 do
+    writeln('TCSSResolverParser.CheckSelectorCall_NthChild Arg[',i,'] ',GetCSSObj(aCall.Args[i]),' AsString=',aCall.Args[i].AsString);
+  {$ENDIF}
+
+  // An, An+B, An+B of S, odd, even
+  i:=0;
+  aModulo:=0;
+  aStart:=1;
+  // check step
+  if ArgCount<=i then
+  begin
+    NthWarn(20220915143843,'missing arguments',aCall);
+    exit;
+  end;
+  Arg:=aCall.Args[i];
+  if Arg.ClassType=TCSSIntegerElement then
+  begin
+    aModulo:=TCSSIntegerElement(Arg).Value;
+    inc(i);
+    // check n
+    if ArgCount<=i then
+    begin
+      NthWarn(20220915143843,'missing arguments',aCall);
+      exit;
+    end;
+    Arg:=aCall.Args[i];
+    if Arg.ClassType<>TCSSResolvedIdentifierElement then
+    begin
+      NthWarn(20220915144312,'expected n',Arg);
+      exit;
+    end;
+    if TCSSResolvedIdentifierElement(Arg).Value<>'n' then
+    begin
+      NthWarn(20220915144359,'expected n',Arg);
+      exit;
+    end;
+
+  end
+  else if Arg.ClassType=TCSSResolvedIdentifierElement then
+  begin
+    Str:=TCSSResolvedIdentifierElement(Arg).Value;
+    case lowercase(Str) of
+    'even':
+      begin
+      aModulo:=2;
+      aStart:=2;
+      end;
+    'odd':
+      begin
+      aModulo:=2;
+      end;
+    'n':
+      begin
+      aModulo:=1;
+      end;
+    else
+      NthWarn(20220915150332,'expected multiplier',Arg);
+      exit;
+    end
+  end else if Arg.ClassType=TCSSUnaryElement then
+  begin
+    anUnary:=TCSSUnaryElement(Arg);
+    case anUnary.Operation of
+    uoMinus: aModulo:=-1;
+    uoPlus: aModulo:=1;
+    else
+      NthWarn(20220917080309,'expected multiplier',Arg);
+      exit;
+    end;
+    if (anUnary.Right.ClassType=TCSSResolvedIdentifierElement)
+        and (SameText(TCSSResolvedIdentifierElement(anUnary.Right).Value,'n')) then
+    begin
+      // ok
+    end else begin
+      NthWarn(20220917080154,'expected multiplier',Arg);
+      exit;
+    end;
+  end else
+  begin
+    NthWarn(20220915144056,'expected multiplier',Arg);
+    exit;
+  end;
+
+  inc(i);
+  if ArgCount>i then
+  begin
+    Arg:=aCall.Args[i];
+    if Arg.ClassType=TCSSUnaryElement then
+    begin
+      UnaryEl:=TCSSUnaryElement(Arg);
+      if not (UnaryEl.Operation in [uoMinus,uoPlus]) then
+      begin
+        NthWarn(20220915151422,'unexpected offset',UnaryEl);
+        exit;
+      end;
+      OffsetEl:=UnaryEl.Right;
+      if OffsetEl=nil then
+      begin
+        NthWarn(20220915151511,'unexpected offset',UnaryEl);
+        exit;
+      end;
+      if OffsetEl.ClassType<>TCSSIntegerElement then
+      begin
+        NthWarn(20220915151718,'unexpected offset',OffsetEl);
+        exit;
+      end;
+      aStart:=TCSSIntegerElement(OffsetEl).Value;
+      if UnaryEl.Operation=uoMinus then
+        aStart:=-aStart;
+    end else
+    begin
+      NthWarn(20220915150851,'expected offset',Arg);
+      exit;
+    end;
+  end;
+
+  Params:=CSSNthChildParamsClass.Create;
+  aCall.Params:=Params;
+  Params.Modulo:=aModulo;
+  Params.Start:=aStart;
+
+  inc(i);
+  if (i<ArgCount) then
+  begin
+    Arg:=aCall.Args[i];
+    if (Arg.ClassType=TCSSResolvedIdentifierElement)
+        and (SameText(TCSSResolvedIdentifierElement(Arg).Value,'of')) then
+    begin
+      // An+B of Selector
+      inc(i);
+      if i=ArgCount then
+      begin
+        NthWarn(20240711154813,'expected selector',Arg);
+        exit;
+      end;
+      Arg:=aCall.Args[i];
+      Params.HasOf:=true;
+      Params.OfSelector:=Arg;
+    end;
+  end;
+
+  if (aCall.NameNumericalID in [CSSCallID_NthOfType,CSSCallID_NthLastOfType]) then
+    Params.HasOf:=true;
+end;
+
+function TCSSResolverParser.ComputeValue(El: TCSSElement): TCSSString;
+var
+  ElData: TObject;
+  C: TClass;
+  StrEl: TCSSStringElement;
+  IntEl: TCSSIntegerElement;
+  FloatEl: TCSSFloatElement;
+begin
+  C:=El.ClassType;
+  if C=TCSSResolvedIdentifierElement then
+    Result:=TCSSResolvedIdentifierElement(El).Value
+  else if (C=TCSSStringElement)
+      or (C=TCSSIntegerElement)
+      or (C=TCSSFloatElement) then
+  begin
+    ElData:=El.CustomData;
+    if ElData is TCSSValueData then
+      exit(TCSSValueData(ElData).NormValue);
+    if C=TCSSStringElement then
+    begin
+      StrEl:=TCSSStringElement(El);
+      Result:=StrEl.Value;
+    end
+    else if C=TCSSIntegerElement then
+    begin
+      IntEl:=TCSSIntegerElement(El);
+      Result:=IntEl.AsString;
+    end else if C=TCSSFloatElement then
+    begin
+      FloatEl:=TCSSFloatElement(El);
+      Result:=FloatEl.AsString;
+    end;
+    ElData:=TCSSValueData.Create;
+    TCSSValueData(ElData).NormValue:=Result;
+    El.CustomData:=ElData;
+  end else begin
+    Log(etWarning,20240625162632,'TCSSResolverParser.ComputeValue not supported',El);
+  end;
+end;
+
+constructor TCSSResolverParser.Create(AScanner: TCSSScanner);
+begin
+  inherited Create(AScanner);
+  CSSIdentifierElementClass:=TCSSResolvedIdentifierElement;
+  CSSPseudoClassElementClass:=TCSSResolvedPseudoClassElement;
+  CSSCallElementClass:=TCSSResolvedCallElement;
+  CSSNthChildParamsClass:=TCSSNthChildParams;
+  CSSAttributeKeyDataClass:=TCSSAttributeKeyData;
+end;
+
+destructor TCSSResolverParser.Destroy;
+begin
+  inherited Destroy;
+end;
+
+procedure TCSSResolverParser.Log(MsgType: TEventType; const ID: TCSSMsgID;
+  const Msg: TCSSString; PosEl: TCSSElement);
+begin
+  if Assigned(OnLog) then
+    OnLog(MsgType,ID,Msg,PosEl);
+end;
+
+class function TCSSResolverParser.IsWhiteSpace(const s: TCSSString): boolean;
+var
+  i: Integer;
+begin
+  for i:=1 to length(s) do
+    if not (s[i] in [' ',#10,#13]) then
+      exit(false);
+  Result:=true;
+end;
+
+end.
+

+ 163 - 124
src/base/fcl-css/fpcssscanner.pp

@@ -79,13 +79,10 @@ Type
     ctkPIPE,
     ctkPIPEEQUAL,
     ctkDOLLAR,
-    ctkDOLLAREQUAL,
-    ctkINVALID
+    ctkDOLLAREQUAL
    );
   TCSSTokens = Set of TCSSToken;
 
-  TCSSString = UTF8String;
-
 resourcestring
   SErrInvalidCharacter = 'Invalid character ''%s''';
   SErrOpenString = 'String exceeds end of line';
@@ -130,13 +127,12 @@ Type
 
   { TCSSScanner }
 
-  TCSSScannerOption = (csoExtendedIdentifiers,csoReturnComments,csoReturnWhiteSpace);
+  TCSSScannerOption = (csoExtendedIdentifiers,csoReturnComments,csoReturnWhiteSpace,csoDisablePseudo);
   TCSSScannerOptions = set of TCSSScannerOption;
-  TCSSScannerWarnEvent = procedure(Sender: TObject; Msg: string) of object;
+  TCSSScannerWarnEvent = procedure(Sender: TObject; Msg: TCSSString) of object;
 
   TCSSScanner = class
   private
-    FDisablePseudo: Boolean;
     FOnWarn: TCSSScannerWarnEvent;
     FOptions: TCSSScannerOptions;
     FSourceFile: TLineReader;
@@ -145,7 +141,7 @@ Type
     FCurToken: TCSSToken;
     FCurTokenString: TCSSString;
     FCurLine: TCSSString;
-    TokenStr: PAnsiChar;
+    TokenStr: PCSSChar;
     FSourceStream : TStream;
     FOwnSourceFile : Boolean;
     function DoHash: TCSSToken;
@@ -156,17 +152,16 @@ Type
     function DoNumericLiteral: TCSSToken;
     function DoSingleLineComment: TCSSToken;
     function DoStringLiteral: TCSSToken;
+    function DoStringEscape: TCSSToken;
     function DoWhiteSpace: TCSSToken;
     function EatBadURL: TCSSToken;
     Function DoUnicodeRange : TCSSTOKEN;
     function FetchLine: Boolean;
     function GetCurColumn: Integer;
-    function GetReturnComments: Boolean;
-    function GetReturnWhiteSpace: Boolean;
+    function GetOption(anOption: TCSSScannerOption): Boolean;
     function ReadUnicodeEscape: WideChar;
-    procedure SetReturnComments(AValue: Boolean);
-    procedure SetReturnWhiteSpace(AValue: Boolean);
-    class function UnknownCharToStr(C: AnsiChar): TCSSString;
+    procedure SetOption(anOption: TCSSScannerOption; const AValue: Boolean);
+    class function UnknownCharToStr(C: TCSSChar): TCSSString;
   protected
     procedure DoError(const Msg: TCSSString; Args: array of const); overload;
     procedure DoError(const Msg: TCSSString); overload;
@@ -178,8 +173,8 @@ Type
     procedure OpenFile(const AFilename: TCSSString);
     Function FetchToken: TCSSToken;
     function IsUTF8BOM: boolean;
-    Property ReturnComments : Boolean Read GetReturnComments Write SetReturnComments;
-    Property ReturnWhiteSpace : Boolean Read GetReturnWhiteSpace Write SetReturnWhiteSpace;
+    Property ReturnComments : Boolean Index csoReturnComments Read GetOption Write SetOption;
+    Property ReturnWhiteSpace : Boolean Index csoReturnWhiteSpace Read GetOption Write SetOption;
     Property Options : TCSSScannerOptions Read FOptions Write FOptions;
     property SourceFile: TLineReader read FSourceFile;
     property CurFilename: TCSSString read FSourceFilename;
@@ -188,11 +183,11 @@ Type
     property CurColumn: Integer read GetCurColumn;
     property CurToken: TCSSToken read FCurToken;
     property CurTokenString: TCSSString read FCurTokenString;
-    property DisablePseudo : Boolean Read FDisablePseudo Write FDisablePseudo;
+    property DisablePseudo : Boolean Index csoDisablePseudo Read GetOption Write SetOption;
     property OnWarn: TCSSScannerWarnEvent read FOnWarn write FOnWarn;
   end;
 
-function SafeFormat(const Fmt: string; const Args: array of const): string;
+function SafeFormat(const Fmt: TCSSString; const Args: array of const): TCSSString;
 
 implementation
 
@@ -200,11 +195,11 @@ Const
   Alpha = ['A'..'Z','a'..'z'];
   Num   = ['0'..'9'];
   AlNum = Alpha+Num;
-  AlNumIden = Alpha+Num+['-'];
+  AlNumIden = AlNum+['-'];
   WhiteSpace = [' ',#9];
 
 type
-  TMessageArgs = array of string;
+  TMessageArgs = array of TCSSString;
 
 procedure CreateMsgArgs(var MsgArgs: TMessageArgs; const Args: array of const);
 var
@@ -281,7 +276,8 @@ begin
     {$endif}
 end;
 
-function SafeFormat(const Fmt: string; const Args: array of const): string;
+function SafeFormat(const Fmt: TCSSString;
+  const Args: array of const): TCSSString;
 var
   MsgArgs: TMessageArgs;
   i: Integer;
@@ -335,7 +331,7 @@ end;
 
 constructor TCSSScanner.Create(AStream: TStream);
 begin
-  FSourceStream:=ASTream;
+  FSourceStream:=AStream;
   FOwnSourceFile:=True;
   Create(TStreamLineReader.Create(AStream));
 end;
@@ -343,7 +339,7 @@ end;
 destructor TCSSScanner.Destroy;
 begin
   If FOwnSourceFile then
-    FSourceFile.Free;
+    FreeAndNil(FSourceFile);
   inherited Destroy;
 end;
 
@@ -363,7 +359,7 @@ begin
   end else
   begin
     FCurLine := FSourceFile.ReadLine;
-    TokenStr := PAnsiChar(CurLine);
+    TokenStr := PCSSChar(CurLine);
     Result := true;
     Inc(FCurRow);
   end;
@@ -387,7 +383,7 @@ end;
 function TCSSScanner.DoSingleLineComment : TCSSToken;
 
 Var
-  TokenStart : PAnsiChar;
+  TokenStart : PCSSChar;
   Len : Integer;
 
 begin
@@ -405,9 +401,9 @@ end;
 function TCSSScanner.DoMultiLineComment : TCSSToken;
 
 Var
-  TokenStart : PAnsiChar;
+  TokenStart : PCSSChar;
   Len,OLen : Integer;
-  PrevToken : AnsiChar;
+  PrevToken : TCSSChar;
 
 begin
   Inc(TokenStr);
@@ -485,28 +481,20 @@ begin
   Result:=WideChar(StrToInt('$'+S));
 end;
 
-procedure TCSSScanner.SetReturnComments(AValue: Boolean);
+procedure TCSSScanner.SetOption(anOption: TCSSScannerOption; const AValue: Boolean
+  );
 begin
   if AValue then
-    Include(FOptions,csoReturnComments)
+    Include(FOptions,anOption)
   else
-    Exclude(FOptions,csoReturnComments)
+    Exclude(FOptions,anOption);
 end;
 
-procedure TCSSScanner.SetReturnWhiteSpace(AValue: Boolean);
-begin
-  if AValue then
-    Include(FOptions,csoReturnWhiteSpace)
-  else
-    Exclude(FOptions,csoReturnWhiteSpace)
-end;
-
-
 function TCSSScanner.DoStringLiteral: TCSSToken;
 
 Var
-  Delim : AnsiChar;
-  TokenStart : PAnsiChar;
+  Delim : TCSSChar;
+  TokenStart : PCSSChar;
   Len,OLen: Integer;
   S : TCSSString;
 
@@ -541,7 +529,7 @@ begin
         Move(TokenStart^, FCurTokenString[OLen + 1], Len);
       Move(S[1],FCurTokenString[OLen + Len+1],Length(S));
       Inc(OLen, Len+Length(S));
-      // Next AnsiChar
+      // Next char
       // Inc(TokenStr);
       TokenStart := TokenStr+1;
       end;
@@ -559,58 +547,105 @@ begin
   Result := ctkSTRING;
 end;
 
-function TCSSScanner.DoNumericLiteral :TCSSToken;
+function TCSSScanner.DoStringEscape: TCSSToken;
+var
+  TokenStart: PCSSChar;
+  Len: Integer;
+begin
+  Inc(TokenStr); // skip \
+  TokenStart := TokenStr;
+  while TokenStr[0] in Num do
+    inc(TokenStr);
+  Len:=TokenStr-TokenStart;
+  Setlength(FCurTokenString, Len);
+  if (Len>0) then
+    Move(TokenStart^,FCurTokenString[1],Len);
+  Result:=ctkString;
+  FCurTokenString:=TCSSChar(StrToInt(FCurTokenString));
+end;
+
+function TCSSScanner.DoNumericLiteral: TCSSToken;
+// number: 1, 0.2, .3, 4.01, 0.0, +0.0, -0.0, .50, 2e3, -6.7E-2
+const
+  NumEnd = [#0..#31,' ',';','{','}',
+    ',',
+    ')', // e.g. calc(3*4)
+    '*','/', // e.g. 3*4, note that + and - require whitespace
+    'a'..'z','A'..'Z' // e.g. 3px
+    ];
+
+  procedure Skip;
+  begin
+    while not (TokenStr^ in NumEnd) do inc(TokenStr);
+    Result:=ctkUNKNOWN;
+  end;
 
 Var
-  TokenStart : PAnsiChar;
+  TokenStart : PCSSChar;
   Len : Integer;
-  isEscape : Boolean;
+  HasNumber: Boolean;
 
 begin
   Result := ctkINTEGER;
-  isEscape:=TokenStr[0]='\';
-  if IsEscape then
-    Inc(TokenStr);
   TokenStart := TokenStr;
-  while true do
+  case TokenStr^ of
+  '-': inc(TokenStr);
+  '+': if csoReturnWhiteSpace in Options then inc(TokenStr);
+  end;
+  HasNumber:=false;
+  if TokenStr^ in Num then
     begin
-    Inc(TokenStr);
-    case TokenStr[0] of
-      '.':
-        if IsEscape then
-          Break
-        else
-          begin
-            Result := ctkFLOAT;
-            if TokenStr[1] in ['0'..'9'] then
-            begin
-              Inc(TokenStr);
-              repeat
-                Inc(TokenStr);
-              until not (TokenStr[0] in ['0'..'9']);
-            end;
-            break;
-          end;
-      '0'..'9': ;
-      else
-        break;
+    // read significand
+    HasNumber:=true;
+    repeat
+      inc(TokenStr);
+    until not (TokenStr^ in Num);
     end;
-  end;
+  if TokenStr^='.' then
+    begin
+    // read fraction
+    inc(TokenStr);
+    if TokenStr^ in Num then
+      begin
+      Result := ctkFLOAT;
+      HasNumber:=true;
+      repeat
+        inc(TokenStr);
+      until not (TokenStr^ in Num)
+      end;
+    end;
+  if not HasNumber then
+    begin
+    Skip;
+    exit;
+    end;
+  if (TokenStr^ in ['e','E']) and not (TokenStr[1] in Alpha) then
+    begin
+    // read exponent
+    Result := ctkFLOAT;
+    inc(TokenStr);
+    if TokenStr^ in ['-','+'] then
+      inc(TokenStr);
+    if not (TokenStr^ in Num) then
+      begin
+      Skip;
+      exit;
+      end;
+    repeat
+      inc(TokenStr);
+    until not (TokenStr^ in Num)
+    end;
+
   Len:=TokenStr-TokenStart;
   Setlength(FCurTokenString, Len);
   if (Len>0) then
-  Move(TokenStart^,FCurTokenString[1],Len);
-  if IsEscape then
-    begin
-    Result:=ctkString;
-    FCurTokenString:=AnsiChar(StrToInt(FCurTokenString));
-    end;
+    Move(TokenStart^,FCurTokenString[1],Len);
 end;
 
 function TCSSScanner.DoHash :TCSSToken;
 
 Var
-  TokenStart : PAnsiChar;
+  TokenStart : PCSSChar;
   Len : Integer;
 
 begin
@@ -629,7 +664,7 @@ end;
 function TCSSScanner.EatBadURL: TCSSToken;
 
 var
-  TokenStart : PAnsiChar;
+  TokenStart : PCSSChar;
   C : AnsiChar;
   len,oldlen : integer;
 
@@ -658,9 +693,9 @@ end;
 
 function TCSSScanner.DoUnicodeRange: TCSSTOKEN;
 Var
-  TokenStart:PAnsiChar;
+  TokenStart: PCSSChar;
   Len : Integer;
-  Tokens : Set of AnsiChar;
+  Tokens : Set of TCSSChar;
 
 begin
   Tokens:= ['A'..'F', 'a'..'f', '0'..'9', '-'];
@@ -680,7 +715,7 @@ begin
 
 end;
 
-class function TCSSScanner.UnknownCharToStr(C: AnsiChar): TCSSString;
+class function TCSSScanner.UnknownCharToStr(C: TCSSChar): TCSSString;
 
 begin
   if C=#0 then
@@ -694,7 +729,7 @@ end;
 function TCSSScanner.DoIdentifierLike : TCSSToken;
 
 Var
-  TokenStart:PAnsiChar;
+  TokenStart: PCSSChar;
   Len,oLen : Integer;
   IsEscape,IsAt, IsPseudo, IsFunc : Boolean;
 
@@ -744,7 +779,17 @@ begin
     Result:=ctkATKEYWORD
   else if CurTokenString='!important' then
     Result:=ctkIMPORTANT
-  else if (CurtokenString='url(') then
+  else if CurTokenString='!' then
+    begin
+    if (TokenStr^=' ') and CompareMem(TokenStr+1,PChar('important'),9)
+    and (TokenStr[10] in [' ',';']) then
+      begin
+      inc(TokenStr,10);
+      FCurTokenString:='!important';
+      Result:=ctkIMPORTANT;
+      end;
+    end
+  else if (CurTokenString='url(') then
     begin
     Result:=ctkURL;
     If TokenStr[0] in ['"',''''] then
@@ -769,13 +814,13 @@ end;
 
 function TCSSScanner.DoInvalidChars: TCSSToken;
 var
-  TokenStart: PAnsiChar;
+  TokenStart: PCSSChar;
   Len: SizeUInt;
 begin
-  Result:=ctkINVALID;
+  Result:=ctkUNKNOWN;
   TokenStart := TokenStr;
   repeat
-    writeln('TCSSScanner.DoInvalidChars ',hexstr(ord(TokenStr^),2));
+    //writeln('TCSSScanner.DoInvalidChars ',hexstr(ord(TokenStr^),2));
     Inc(TokenStr);
   until (TokenStr[0] in [#0,#9,#10,#13,#32..#127]);
   Len:=TokenStr-TokenStart;
@@ -792,7 +837,7 @@ var
 begin
   Repeat
     Result:=DoFetchToken;
-    if (Result=ctkINVALID) and IsUTF8BOM then
+    if (Result=ctkUNKNOWN) and IsUTF8BOM then
       CanStop:=false
     else
       CanStop:=(Not (Result in [ctkComment,ctkWhiteSpace]))
@@ -843,35 +888,35 @@ begin
   //CurPos:=TokenStr;
   FCurTokenString := '';
   case TokenStr[0] of
-    #0:         // Empty line
+    #0:         // EOL
       begin
       FetchLine;
       Result := ctkWhitespace;
       end;
     '''','"':
-       Result:=DoStringLiteral;
+      Result:=DoStringLiteral;
     '/' :
-       Result:=CommentDiv;
+      Result:=CommentDiv;
     #9, ' ':
-       Result := DoWhiteSpace;
+      Result := DoWhiteSpace;
     '#':
-       Result:=DoHash;
+      Result:=DoHash;
     '\':
-       begin
-       if TokenStr[1] in ['0'..'9'] then
-         Result:=DoNumericLiteral
-       else
-         begin
-         if (TokenStr[1] in WhiteSpace) or (TokenStr[1]=#0) then
-           DoError(SErrUnknownCharacter ,[UnknownCharToStr(TokenStr[1])])
-         else
-           Result:=DoIdentifierLike
-         end;
-       end;
+      begin
+      if TokenStr[1] in ['0'..'9'] then
+        Result:=DoStringEscape
+      else
+        begin
+        if (TokenStr[1] in WhiteSpace) or (TokenStr[1]=#0) then
+          DoError(SErrUnknownCharacter ,[UnknownCharToStr(TokenStr[1])])
+        else
+          Result:=DoIdentifierLike
+        end;
+      end;
     '0'..'9':
-       Result:=DoNumericLiteral;
+      Result:=DoNumericLiteral;
     '&': CharToken(ctkAnd);
-    '{': CharToken( ctkLBRACE);
+    '{': CharToken(ctkLBRACE);
     '}': CharToken(ctkRBRACE);
     '*': if TokenStr[1]='=' then
            TwoCharsToken(ctkSTAREQUAL)
@@ -904,7 +949,7 @@ begin
     '@': Result:=DoIdentifierLike;
     ':':
       begin
-      if DisablePseudo then
+      if csoDisablePseudo in Options then
         CharToken(ctkCOLON)
       else if (TokenStr[1]=':') then
         begin
@@ -920,8 +965,10 @@ begin
       end;
     '.':
       begin
-      if (TokenStr[1] in AlNum) then
-        Result:=Self.DoIdentifierLike
+      if TokenStr[1] in Num then
+        Result:=DoNumericLiteral  // e.g. .1 = 0.1
+      else if TokenStr[1] in Alpha then
+        Result:=DoIdentifierLike
       else
         CharToken(ctkDOT);
       end;
@@ -945,16 +992,17 @@ begin
       '0'..'9':
         Result:=DoNumericLiteral;
       '.':
-        if TokenStr[2] in ['0'..'9'] then
+        if TokenStr[2] in Num then
           Result:=DoNumericLiteral
         else
           CharToken(ctkMINUS);
-      #9,' ',#0:
+      #9,#10,#13,' ',#0:
         CharToken(ctkMINUS);
       else
         Result:=DoIdentifierLike;
       end;
-    '+': CharToken(ctkPLUS);
+    '+':
+      CharToken(ctkPLUS);
     '%': CharToken(ctkPERCENTAGE);
     '_','!',
     'a'..'z',
@@ -966,12 +1014,8 @@ begin
          Result:=DoIdentifierLike;
        end;
   else
-    writeln('TCSSScanner.DoFetchToken ',Ord(TokenStr[0]));
-    If Ord(TokenStr[0])>127 then
-      Result:=DoInvalidChars
-    else
-      DoError(SErrUnknownCharacter ,['"'+TokenStr[0]+'"']);
-
+    //writeln('TCSSScanner.DoFetchToken ',Ord(TokenStr[0]));
+    Result:=DoInvalidChars;
   end; // Case
 end;
 
@@ -995,17 +1039,12 @@ begin
   if (TokenStr=Nil) or (Length(CurLine)=0) then
     Result:=0
   else
-    Result := TokenStr - PAnsiChar(CurLine);
-end;
-
-function TCSSScanner.GetReturnComments: Boolean;
-begin
-  Result:=(csoReturnComments in FOptions);
+    Result := TokenStr - PCSSChar(CurLine);
 end;
 
-function TCSSScanner.GetReturnWhiteSpace: Boolean;
+function TCSSScanner.GetOption(anOption: TCSSScannerOption): Boolean;
 begin
-  Result:=(csoReturnWhiteSpace in FOptions);
+  Result:=anOption in Options;
 end;
 
 { TStreamLineReader }

+ 198 - 13
src/base/fcl-css/fpcsstree.pp

@@ -18,7 +18,6 @@ unit fpCSSTree;
 {$ENDIF FPC_DOTTEDUNITS}
 
 {$mode ObjFPC}{$H+}
-{$codepage utf8}
 
 interface
 
@@ -29,12 +28,195 @@ uses Contnrs, RtlConsts, SysUtils, Classes, Math;
 {$ENDIF FPC_DOTTEDUNITS}
 
 
+const
+  CSSFormatSettings: TFormatSettings = (
+    CurrencyFormat: 1;
+    NegCurrFormat: 5;
+    ThousandSeparator: ',';
+    DecimalSeparator: '.';
+    CurrencyDecimals: 2;
+    DateSeparator: '-';
+    TimeSeparator: ':';
+    ListSeparator: ',';
+    CurrencyString: '$';
+    ShortDateFormat: 'd/m/y';
+    LongDateFormat: 'dd" "mmmm" "yyyy';
+    TimeAMString: 'AM';
+    TimePMString: 'PM';
+    ShortTimeFormat: 'hh:nn';
+    LongTimeFormat: 'hh:nn:ss';
+    ShortMonthNames: ('Jan','Feb','Mar','Apr','May','Jun',
+                      'Jul','Aug','Sep','Oct','Nov','Dec');
+    LongMonthNames: ('January','February','March','April','May','June',
+                     'July','August','September','October','November','December');
+    ShortDayNames: ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
+    LongDayNames:  ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
+    TwoDigitYearCenturyWindow: 50;
+  );
+
 Type
   ECSSException = class(Exception);
 
-  TCSSString = UTF8String;
-  TCSSStringDynArray = array of TCSSString;
-  TCSSUnits = (cuNONE, cuPX,cuPERCENT,cuREM,cuEM,cuPT,cuFR,cuVW,cuVH,cuDEG);
+  {$IF FPC_FULLVERSION>30300}
+  TCSSChar = Char;
+  TCSSString = String; // can be AnsiString or UnicodeString
+  {$ELSE}
+  TCSSChar = Char;
+  TCSSString = String;
+  {$ENDIF}
+  PCSSChar = ^TCSSChar;
+  TCSSStringArray = array of TCSSString;
+
+  TCSSUnit = (
+    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,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
+    // 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
   TCSSType = (
     csstUnknown,
     csstInteger, csstString, csstFloat,
@@ -145,7 +327,7 @@ Type
   TCSSIntegerElement = class(TCSSElement)
   private
     FIsEscaped: Boolean;
-    FUnits: TCSSUnits;
+    FUnits: TCSSUnit;
     FValue: Integer;
   protected
     function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString; override;
@@ -154,7 +336,7 @@ Type
     function Equals(Obj: TObject): boolean; override;
     Property Value : Integer Read FValue Write FValue;
     Property IsEscaped : Boolean Read FIsEscaped Write FIsEscaped;
-    Property Units : TCSSUnits Read FUnits Write FUnits;
+    Property Units : TCSSUnit Read FUnits Write FUnits;
   end;
   TCSSIntegerElementClass = class of TCSSIntegerElement;
 
@@ -162,7 +344,7 @@ Type
 
   TCSSFloatElement = class(TCSSElement)
   private
-    FUnits: TCSSUnits;
+    FUnits: TCSSUnit;
     FValue: Double;
   protected
     function GetAsString(aFormat : Boolean; const aIndent : TCSSString): TCSSString;override;
@@ -170,7 +352,7 @@ Type
     Class function CSSType : TCSSType; override;
     function Equals(Obj: TObject): boolean; override;
     Property Value : Double Read FValue Write FValue;
-    Property Units : TCSSUnits Read FUnits Write FUnits;
+    Property Units : TCSSUnit Read FUnits Write FUnits;
   end;
   TCSSFloatElementClass = class of TCSSFloatElement;
 
@@ -448,14 +630,14 @@ Function StringToCSSString(const S : TCSSString) : TCSSString;
 // Escapes non-identifier characters C to \C
 Function StringToIdentifier(const S : TCSSString) : TCSSString;
 
+function FloatToCSSStr(const f: double): string;
+
 Function GetCSSObj(El: TCSSElement): TCSSString;
 Function GetCSSPath(El: TCSSElement): TCSSString;
 
 Function CSSElementListEquals(ListA, ListB: TCSSElementList): boolean;
 
 Const
-  CSSUnitNames : Array[TCSSUnits] of TCSSString =
-        ('','px','%','rem','em','pt','fr','vw','vh','deg');
   UnaryOperators : Array[TCSSUnaryOperation] of TCSSString =
         ('::','-','+','/','>','~');
   BinaryOperators : Array[TCSSBinaryOperation] of TCSSString =
@@ -566,6 +748,11 @@ begin
   SetLength(Result,iOut);
 end;
 
+function FloatToCSSStr(const f: double): string;
+begin
+  Result:=FloatToStr(f,CSSFormatSettings);
+end;
+
 function GetCSSObj(El: TCSSElement): TCSSString;
 begin
   if El=nil then
@@ -1075,9 +1262,7 @@ end;
 function TCSSFloatElement.GetAsString(aFormat: Boolean;
   const aIndent: TCSSString): TCSSString;
 begin
-  Str(Value:5:2,Result);
-  Result:=TrimLeft(Result); // Space for positive numbers
-  Result:=Result+CSSUnitNames[Units];
+  Result:=FloatToCSSStr(Value)+CSSUnitNames[Units];
   if aFormat then
     Result:=aIndent+Result;
 end;

+ 26 - 4
src/base/fresnel.classes.pas

@@ -24,7 +24,7 @@ unit Fresnel.Classes;
 interface
 
 uses
-  Classes, SysUtils, Math, Types, fpCSSScanner;
+  Classes, SysUtils, Math, Types, FpImage, System.UITypes, fpCSSScanner, fpCSSResParser;
 
 type
   {$IF FPC_FULLVERSION<30301}
@@ -41,7 +41,6 @@ type
 
   TCalcBoolean = (cbCalc,cbFalse,cbTrue);
 
-
 const
   MaxFresnelLength = TFresnelLength(high(longint));
 
@@ -174,14 +173,17 @@ type
   TFLComponent = TFresnelComponent;
 
 
-function FloatToCSSStr(const f: TFresnelLength): string; // todo: use fpCSSParser
-function CSSStrToFloat(const s: string; out l: TFresnelLength): boolean; // todo: use fpCSSParser
+function FloatToCSSStr(const f: TFresnelLength): string;
+function FloatToCSSPx(const p: TFresnelLength): string;
+function CSSStrToFloat(const s: string; out l: TFresnelLength): boolean;
 function CompareFresnelPoint(const A, B: TFresnelPoint): integer;
 function CompareFresnelRect(const A, B: TFresnelRect): integer;
 
 function PointFre(const X, Y: TFresnelLength): TFresnelPoint; overload;
 function RectFre(const Left, Top, Right, Bottom: TFresnelLength): TFresnelRect; overload;
 
+function FPColor(c: TCSSAlphaColor): TFPColor; overload;
+
 Procedure FLLog(aType: TEventType; Const Msg : string); overload;
 Procedure FLLog(aType: TEventType; Const Fmt : string; Const Args : Array of const); overload;
 Procedure FLLog(aType: TEventType; const args : Array of string); overload;
@@ -204,6 +206,11 @@ begin
   Result:=FloatToStr(f,FresnelCSSFormatSettings);
 end;
 
+function FloatToCSSPx(const p: TFresnelLength): string;
+begin
+  Result:=FloatToStr(p,FresnelCSSFormatSettings)+'px';
+end;
+
 function CSSStrToFloat(const s: string; out l: TFresnelLength): boolean;
 var
   Code: Integer;
@@ -268,6 +275,21 @@ begin
   Result.Bottom:=Bottom;
 end;
 
+function FPColor(c: TCSSAlphaColor): TFPColor;
+begin
+  Result.Blue:=c and $ff;
+  Result.Blue:=Result.Blue or (Result.Blue shl 8);
+  c:=c shr 8;
+  Result.Green:=c and $ff;
+  Result.Green:=Result.Green or (Result.Green shl 8);
+  c:=c shr 8;
+  Result.Red:=c and $ff;
+  Result.Red:=Result.Red or (Result.Red shl 8);
+  c:=c shr 8;
+  Result.Alpha:=c and $ff;
+  Result.Alpha:=Result.Alpha or (Result.Alpha shl 8);
+end;
+
 procedure FLLog(aType: TEventType; const Msg: string);
 begin
   TFresnelComponent.DoLog(aType,Msg);

+ 45 - 166
src/base/fresnel.controls.pas

@@ -23,7 +23,7 @@ unit Fresnel.Controls;
 interface
 
 uses
-  Classes, SysUtils, Math, fpCSSResolver, fpCSSTree,
+  Classes, SysUtils, Math, fpCSSResolver, fpCSSTree, fpCSSResParser,
   fpImage, fresnel.images,
   Fresnel.Classes, Fresnel.Dom;
 
@@ -36,11 +36,9 @@ type
     class var FFresnelDivTypeID: TCSSNumericalID;
     class constructor InitFresnelDivClass;
   public
-    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString;
-      override;
-    procedure ClearCSSValues; override;
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   end;
 
   { TSpan - span element }
@@ -50,11 +48,9 @@ type
     class var FFresnelSpanTypeID: TCSSNumericalID;
     class constructor InitFresnelSpanClass;
   public
-    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString;
-      override;
-    procedure ClearCSSValues; override;
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   end;
 
   { TReplacedElement - base class for elements with special content and no child elements, e.g. label, video }
@@ -77,7 +73,6 @@ type
   TCustomLabel = class(TReplacedElement)
   private
     FCaption: TFresnelCaption;
-    FRenderedCaption: TFresnelCaption;
   protected
     FLabelStates: TFresnelLabelStates;
     FMinCaption: String; // Caption with linebreak after each word
@@ -93,12 +88,7 @@ type
     function GetMinWidthIntrinsicContentBox: TFresnelLength; override;
     function GetPreferredContentBox_MaxWidth(MaxWidth: TFresnelLength): TFresnelPoint;
       override;
-    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString;
-      override;
-    procedure ClearCSSValues; override;
-    procedure UpdateRenderedAttributes; override;
     property Caption: TFresnelCaption read FCaption write SetCaption;
-    property RenderedCaption: TFresnelCaption read FRenderedCaption write FRenderedCaption;
   end;
 
   { TLabel - label element }
@@ -110,6 +100,7 @@ type
   public
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   published
     property Caption;
   end;
@@ -120,13 +111,10 @@ type
   private
     class var FFresnelBodyTypeID: TCSSNumericalID;
     class constructor InitFresnelBodyClass;
-  protected
-    procedure SetCSSElAttribute(Attr: TFresnelCSSAttribute; const AValue: string); override;
   public
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
-    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString; override;
-    procedure ClearCSSValues; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   end;
 
 
@@ -165,6 +153,7 @@ type
   public
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   Published
     Property Caption;
     Property Icon;
@@ -183,7 +172,6 @@ type
   Public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
-    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString; override;
     Property Image : TImageData Read FImage Write SetImage;
   end;
 
@@ -196,6 +184,7 @@ type
   public
     class function CSSTypeID: TCSSNumericalID; override;
     class function CSSTypeName: TCSSString; override;
+    class function GetCSSTypeStyle: TCSSString; override;
   Published
     Property Image;
   end;
@@ -224,31 +213,7 @@ end;
 
 class constructor TSpan.InitFresnelSpanClass;
 begin
-  // register type
-  FFresnelSpanTypeID:=RegisterCSSType(CSSTypeName);
-end;
-
-function TSpan.GetCSSInitialAttribute(const AttrID: TCSSNumericalID
-  ): TCSSString;
-var
-  Attr: TFresnelCSSAttribute;
-begin
-  if (AttrID<FresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FresnelElementBaseAttrID) then
-    exit('');
-  Attr:=TFresnelCSSAttribute(AttrID-FresnelElementBaseAttrID);
-  case Attr of
-  fcaDisplayOutside: Result:='inline';
-  fcaDisplayInside: Result:='flow';
-  else
-    Result:=inherited GetCSSInitialAttribute(AttrID);
-  end;
-end;
-
-procedure TSpan.ClearCSSValues;
-begin
-  inherited ClearCSSValues;
-  FCSSAttributes[fcaDisplayOutside]:='inline';
-  FCSSAttributes[fcaDisplayInside]:='flow';
+  FFresnelSpanTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TSpan.CSSTypeID: TCSSNumericalID;
@@ -261,35 +226,16 @@ begin
   Result:='span';
 end;
 
-{ TDiv }
-
-class constructor TDiv.InitFresnelDivClass;
+class function TSpan.GetCSSTypeStyle: TCSSString;
 begin
-  // register type
-  FFresnelDivTypeID:=RegisterCSSType(CSSTypeName);
+  Result:='span { display: inline flow; }';
 end;
 
-function TDiv.GetCSSInitialAttribute(const AttrID: TCSSNumericalID
-  ): TCSSString;
-var
-  Attr: TFresnelCSSAttribute;
-begin
-  if (AttrID<FresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FresnelElementBaseAttrID) then
-    exit('');
-  Attr:=TFresnelCSSAttribute(AttrID-FresnelElementBaseAttrID);
-  case Attr of
-  fcaDisplayOutside: Result:='block';
-  fcaDisplayInside: Result:='';
-  else
-    Result:=inherited GetCSSInitialAttribute(AttrID);
-  end;
-end;
+{ TDiv }
 
-procedure TDiv.ClearCSSValues;
+class constructor TDiv.InitFresnelDivClass;
 begin
-  inherited ClearCSSValues;
-  FCSSAttributes[fcaDisplayOutside]:='block';
-  FCSSAttributes[fcaDisplayInside]:='';
+  FFresnelDivTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TDiv.CSSTypeID: TCSSNumericalID;
@@ -302,35 +248,16 @@ begin
   Result:='div';
 end;
 
-{ TBody }
-
-procedure TBody.SetCSSElAttribute(Attr: TFresnelCSSAttribute;
-  const AValue: string);
-begin
-  case Attr of
-  fcaDisplay,
-  fcaDisplayBox,
-  fcaDisplayInside,
-  fcaDisplayOutside,
-  fcaZIndex,
-  fcaPosition,
-  fcaLeft,
-  fcaTop,
-  fcaBottom,
-  fcaRight,
-  fcaWidth,
-  fcaHeight,
-  fcaMinWidth,
-  fcaMinHeight,
-  fcaMaxWidth,
-  fcaMaxHeight: exit;
-  end;
-  inherited SetCSSElAttribute(Attr,AValue);
+class function TDiv.GetCSSTypeStyle: TCSSString;
+begin
+  Result:='div { display: block; }';
 end;
 
+{ TBody }
+
 class constructor TBody.InitFresnelBodyClass;
 begin
-  FFresnelBodyTypeID:=RegisterCSSType(CSSTypeName);
+  FFresnelBodyTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TBody.CSSTypeID: TCSSNumericalID;
@@ -343,29 +270,9 @@ begin
   Result:='body';
 end;
 
-function TBody.GetCSSInitialAttribute(const AttrID: TCSSNumericalID
-  ): TCSSString;
-var
-  Attr: TFresnelCSSAttribute;
-begin
-  if (AttrID<FresnelElementBaseAttrID) or (AttrID>FresnelElementBaseAttrID+ord(High(TFresnelCSSAttribute))) then
-    exit('');
-  Attr:=TFresnelCSSAttribute(AttrID-FresnelElementBaseAttrID);
-  case Attr of
-  fcaBackgroundColor: Result:='white';
-  fcaColor: Result:='black';
-  fcaDisplayOutside: Result:='block';
-  fcaPosition: Result:='absolute';
-  else
-    Result:=inherited GetCSSInitialAttribute(AttrID);
-  end;
-end;
-
-procedure TBody.ClearCSSValues;
+class function TBody.GetCSSTypeStyle: TCSSString;
 begin
-  inherited ClearCSSValues;
-  FCSSAttributes[fcaDisplayOutside]:='block';
-  FCSSAttributes[fcaPosition]:='absolute';
+  Result:='body { background-color: white; color: black; display: block; position: absolute; }';
 end;
 
 
@@ -430,7 +337,7 @@ end;
 
 class constructor TButton.InitFresnelButtonClass;
 begin
-  FFresnelButtonTypeID:=RegisterCSSType(CSSTypeName);
+  FFresnelButtonTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TButton.CSSTypeID: TCSSNumericalID;
@@ -443,6 +350,11 @@ begin
   Result:='button';
 end;
 
+class function TButton.GetCSSTypeStyle: TCSSString;
+begin
+  Result:='';
+end;
+
 { TCustomImage }
 
 procedure TCustomImage.SetImage(AValue: TImageData);
@@ -471,26 +383,11 @@ begin
   inherited Destroy;
 end;
 
-function TCustomImage.GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString;
-var
-  Attr: TFresnelCSSAttribute;
-begin
-  if (AttrID<FresnelElementBaseAttrID) or (AttrID>FresnelElementBaseAttrID+ord(High(TFresnelCSSAttribute))) then
-    exit('');
-  Attr:=TFresnelCSSAttribute(AttrID-FresnelElementBaseAttrID);
-  case Attr of
-    fcaDisplayOutside: Result:='block';
-    fcaDisplayInside: Result:='';
-  else
-    Result:=inherited GetCSSInitialAttribute(AttrID);
-  end;
-end;
-
 { TImage }
 
 class constructor TImage.InitFresnelImageClass;
 begin
-  FFresnelImageTypeID:=RegisterCSSType(CSSTypeName);
+  FFresnelImageTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TImage.CSSTypeID: TCSSNumericalID;
@@ -503,6 +400,11 @@ begin
   Result:='img';
 end;
 
+class function TImage.GetCSSTypeStyle: TCSSString;
+begin
+  Result:='image { display: block; }';
+end;
+
 { TCustomLabel }
 
 procedure TCustomLabel.ComputeMinCaption;
@@ -584,22 +486,20 @@ end;
 
 procedure TCustomLabel.DoRender(aRenderer: IFresnelRenderer);
 var
-  aColor,aCaption : string;
-  aColorFP , ShadowColor: TFPColor;
+  aCaption : string;
+  aColorFP, ShadowColor: TFPColor;
   aOffsetX, aOffsetY, aRadius: TFresnelLength;
   HaveShadow : Boolean;
 begin
-  aCaption:=RenderedCaption;
+  aCaption:=Caption;
   if aCaption='' then
     exit;
-  aColor:=GetRenderedCSString(fcaColor,true);
-  if not CSSToFPColor(aColor,aColorFP) then
-    aColorFP:=colTransparent;
+  aColorFP:=GetComputedColor(fcaColor,colTransparent);
   if aColorFP.Alpha=alphaTransparent then
     exit;
 
   // Change to loop, later
-  HaveShadow:=GetRenderedCSSTextShadow(aOffsetX, aOffsetY, aRadius, ShadowColor);
+  HaveShadow:=GetComputedTextShadow(aOffsetX, aOffsetY, aRadius, ShadowColor);
   if HaveShadow then
     aRenderer.AddTextShadow(aOffsetX,aOffsetY,ShadowColor,aRadius);
 
@@ -639,40 +539,14 @@ begin
     Result:=FSize
   else
     Result:=Font.TextSizeMaxWidth(FCaption,MaxWidth);
-end;
-
-function TCustomLabel.GetCSSInitialAttribute(const AttrID: TCSSNumericalID
-  ): TCSSString;
-var
-  Attr: TFresnelCSSAttribute;
-begin
-  if (AttrID<FresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FresnelElementBaseAttrID) then
-    exit('');
-  Attr:=TFresnelCSSAttribute(AttrID-FresnelElementBaseAttrID);
-  case Attr of
-  fcaDisplayOutside: Result:='inline';
-  else
-    Result:=inherited GetCSSInitialAttribute(AttrID);
-  end;
-end;
-
-procedure TCustomLabel.ClearCSSValues;
-begin
-  inherited ClearCSSValues;
-  FCSSAttributes[fcaDisplayOutside]:='inline';
-end;
-
-procedure TCustomLabel.UpdateRenderedAttributes;
-begin
-  inherited UpdateRenderedAttributes;
-  FRenderedCaption:=Caption;
+  //writeln('TCustomLabel.GetPreferredContentBox_MaxWidth ',GetPath,' ',Result.ToString);
 end;
 
 { TLabel }
 
 class constructor TLabel.InitFresnelLabelClass;
 begin
-  FFresnelLabelTypeID:=RegisterCSSType(CSSTypeName);
+  FFresnelLabelTypeID:=CSSRegistry.AddType(CSSTypeName).Index;
 end;
 
 class function TLabel.CSSTypeID: TCSSNumericalID;
@@ -685,5 +559,10 @@ begin
   Result:='label';
 end;
 
+class function TLabel.GetCSSTypeStyle: TCSSString;
+begin
+  Result:='label { display: inline flow; }';
+end;
+
 end.
 

文件差异内容过多而无法显示
+ 467 - 228
src/base/fresnel.dom.pas


+ 6 - 0
src/base/fresnel.forms.pas

@@ -650,6 +650,12 @@ procedure TFresnelCustomForm.WSDraw;
 begin
   //FLLog(etDebug,'TFresnelCustomForm.WSDraw (%s)',[ToString]);
   //FLLog(etDebug,'TFresnelCustomForm.WSDraw Have renderer: %b',[Assigned(Renderer)]);
+  LayoutQueued:=false;
+  if DomModified then
+  begin
+    ApplyCSS;
+    Layouter.Apply(Self);
+  end;
   Renderer.Draw(Self);
 end;
 

+ 117 - 197
src/base/fresnel.layouter.pas

@@ -60,9 +60,9 @@ type
   public
     BlockContainer: TFresnelElement;
     Layouter: TFLNodeLayouter;
-    SkipLayout: boolean; // e.g. element or ancestor display-box:none
-    SkipRendering: boolean; // e.g. element or ancestor display-box:none or visibility<>visible
-    SubParent: boolean; // a node in between: has no Layouter and has children
+    SkipLayout: boolean; // e.g. element or ancestor display:none
+    SkipRendering: boolean; // e.g. element or ancestor display:none or visibility<>visible
+    SubParent: boolean; // a node in between: has children but no Layouter
     ZIndex: TFresnelLength;
     destructor Destroy; override;
     function GetMinWidthBorderBox: TFresnelLength; virtual;
@@ -174,7 +174,6 @@ type
     function GetBlockContainer(El: TFresnelElement): TFresnelElement; virtual;
     procedure UpdateLayouter(El: TFresnelElement; LNode: TSimpleFresnelLayoutNode); virtual;
     procedure UpdateLayoutParent(El: TFresnelElement; LNode: TSimpleFresnelLayoutNode); virtual;
-    function GetPixPerUnit(El: TFresnelElement; anUnit: TFresnelCSSUnit; IsHorizontal: boolean): TFresnelLength; override;
     procedure ComputeCSSLayoutNode(El: TFresnelElement); override; // called after basic CSS properties were computed, create the layout nodes
     procedure ComputedChildrenCSS(El: TFresnelElement); override; // called after child nodes CSS properties were computed
     procedure SortStackingContext(LNode: TSimpleFresnelLayoutNode); virtual; // apply z-index
@@ -192,8 +191,6 @@ type
     // margin, border, padding
     // left, top, width, height
     // clipping: left, top, right, bottom
-    // font-size
-    // font-style  normal italic oblique bold
   end;
 
 function CompareLayoutNodesZIndexDomIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
@@ -270,14 +267,14 @@ begin
   FBorderPadValid:=true;
 
   El:=Node.Element;
-  FBorderLeft:=El.GetComputedCSSBorderWidth(fcaBorderLeftWidth);
-  FBorderTop:=El.GetComputedCSSBorderWidth(fcaBorderTopWidth);
-  FBorderRight:=El.GetComputedCSSBorderWidth(fcaBorderRightWidth);
-  FBorderBottom:=El.GetComputedCSSBorderWidth(fcaBorderBottomWidth);
-  FPaddingLeft:=El.GetComputedCSSLength(fcaPaddingLeft,false);
-  FPaddingTop:=El.GetComputedCSSLength(fcaPaddingTop,false);
-  FPaddingRight:=El.GetComputedCSSLength(fcaPaddingRight,false);
-  FPaddingBottom:=El.GetComputedCSSLength(fcaPaddingBottom,false);
+  FBorderLeft:=El.GetComputedBorderWidth(fcaBorderLeftWidth);
+  FBorderTop:=El.GetComputedBorderWidth(fcaBorderTopWidth);
+  FBorderRight:=El.GetComputedBorderWidth(fcaBorderRightWidth);
+  FBorderBottom:=El.GetComputedBorderWidth(fcaBorderBottomWidth);
+  FPaddingLeft:=El.GetComputedLength(fcaPaddingLeft);
+  FPaddingTop:=El.GetComputedLength(fcaPaddingTop);
+  FPaddingRight:=El.GetComputedLength(fcaPaddingRight);
+  FPaddingBottom:=El.GetComputedLength(fcaPaddingBottom);
   FBorderBox.X:=-1;
   FBorderBox.Y:=-1;
 
@@ -339,27 +336,27 @@ begin
     ChildEl:=ChildNode.Element;
 
     ChildTop:=FLineBorderBoxTop-BFCNode.MarginTop;
-    //writeln('TFLBlockFormattingContext.PlaceLineNodes ',i,' ',ChildEl.GetPath,' ',BFCNode.Left,' ',ChildTop,' BFCNode.Height=',BFCNode.ContentBoxHeight);
+    //writeln('TFLBlockFormattingContext.PlaceLineNodes ',i,' ',ChildEl.GetPath,' ',FloatToCSSStr(BFCNode.Left),' ',FloatToCSSStr(ChildTop),' BFCNode.Height=',FloatToCSSStr(BFCNode.ContentBoxHeight));
     ChildBottom:=FLineBorderBoxTop
                 +BFCNode.BorderTop+BFCNode.PaddingTop
                 +BFCNode.ContentBoxHeight
                 +BFCNode.PaddingBottom+BFCNode.BorderBottom+BFCNode.MarginBottom;
 
-    ChildEl.CSSComputedAttribute[fcaLeft]:=FloatToCSSStr(BFCNode.Left);
-    ChildEl.CSSComputedAttribute[fcaRight]:=FloatToCSSStr(BFCNode.Right);
-    ChildEl.CSSComputedAttribute[fcaTop]:=FloatToCSSStr(ChildTop);
-    ChildEl.CSSComputedAttribute[fcaBottom]:=FloatToCSSStr(ChildBottom);
-    ChildEl.CSSComputedAttribute[fcaWidth]:=FloatToCSSStr(BFCNode.ContentBoxWidth);
-    ChildEl.CSSComputedAttribute[fcaHeight]:=FloatToCSSStr(BFCNode.ContentBoxHeight);
+    ChildEl.ComputedAttribute[fcaLeft]:=FloatToCSSPx(BFCNode.Left);
+    ChildEl.ComputedAttribute[fcaRight]:=FloatToCSSPx(BFCNode.Right);
+    ChildEl.ComputedAttribute[fcaTop]:=FloatToCSSPx(ChildTop);
+    ChildEl.ComputedAttribute[fcaBottom]:=FloatToCSSPx(ChildBottom);
+    ChildEl.ComputedAttribute[fcaWidth]:=FloatToCSSPx(BFCNode.ContentBoxWidth);
+    ChildEl.ComputedAttribute[fcaHeight]:=FloatToCSSPx(BFCNode.ContentBoxHeight);
 
     //writeln(
     //  'TFLBlockFormattingContext.PlaceLineNodes '+ChildEl.GetPath+
-    //  ' Left='+ChildEl.CSSComputedAttribute[fcaLeft]+
-    //  ' Top='+ChildEl.CSSComputedAttribute[fcaTop]+
-    //  ' Right='+ChildEl.CSSComputedAttribute[fcaRight]+
-    //  ' Bottom='+ChildEl.CSSComputedAttribute[fcaBottom]+
-    //  ' Width='+ChildEl.CSSComputedAttribute[fcaWidth]+
-    //  ' Height='+ChildEl.CSSComputedAttribute[fcaHeight]
+    //  ' Left='+ChildEl.ComputedAttribute[fcaLeft]+
+    //  ' Top='+ChildEl.ComputedAttribute[fcaTop]+
+    //  ' Right='+ChildEl.ComputedAttribute[fcaRight]+
+    //  ' Bottom='+ChildEl.ComputedAttribute[fcaBottom]+
+    //  ' Width='+ChildEl.ComputedAttribute[fcaWidth]+
+    //  ' Height='+ChildEl.ComputedAttribute[fcaHeight]
     //  );
   end;
 end;
@@ -385,7 +382,7 @@ var
       ChildPrefBorderBox:=ChildNode.GetPreferredBorderBox_MaxWidth(GetViewport.MaxPreferredWidth,flnpsApplyMinMax);
       ChildBorderBoxWidth:=ChildPrefBorderBox.X;
     end else begin
-      ChildMaxBorderBoxWidth:=ChildEl.GetComputedCSSLength(fcaMaxWidth,false,true);
+      ChildMaxBorderBoxWidth:=ChildEl.GetComputedLength(fcaMaxWidth,true);
       if not IsNan(ChildMaxBorderBoxWidth) then
       begin
         ChildMaxBorderBoxWidth:=ChildMaxBorderBoxWidth+ChildPadBorderX;
@@ -404,13 +401,13 @@ var
         ChildPrefBorderBox:=ChildNode.GetPreferredBorderBox_MaxWidth(ChildBorderBoxWidth,flnpsApplyMinMax);
       ChildBorderBoxHeight:=ChildPrefBorderBox.Y;
     end else begin
-      ChildMaxBorderBoxHeight:=ChildEl.GetComputedCSSLength(fcaMaxHeight,false,true);
+      ChildMaxBorderBoxHeight:=ChildEl.GetComputedLength(fcaMaxHeight,true);
       if not IsNan(ChildMaxBorderBoxHeight) then
       begin
         ChildMaxBorderBoxHeight:=ChildMaxBorderBoxHeight+ChildPadBorderY;
         ChildBorderBoxHeight:=Min(ChildBorderBoxHeight,ChildMaxBorderBoxHeight);
       end;
-      ChildMinBorderBoxHeight:=ChildEl.GetComputedCSSLength(fcaMinHeight,false,true);
+      ChildMinBorderBoxHeight:=ChildEl.GetComputedLength(fcaMinHeight,true);
       if not IsNan(ChildMinBorderBoxHeight) then
       begin
         ChildMinBorderBoxHeight:=ChildMinBorderBoxHeight+ChildPadBorderY;
@@ -423,34 +420,37 @@ begin
   ChildEl:=ChildNode.Element;
 
   // left, top, right, bottom are marginbox
-  ChildLeft:=ChildEl.GetComputedCSSLength(fcaLeft,false,true);
-  ChildRight:=ChildEl.GetComputedCSSLength(fcaRight,false,true);
-  ChildTop:=ChildEl.GetComputedCSSLength(fcaTop,false,true);
-  ChildBottom:=ChildEl.GetComputedCSSLength(fcaBottom,false,true);
-
-  ChildMarginLeft:=ChildEl.GetComputedCSSLength(fcaMarginLeft,false);
-  ChildMarginRight:=ChildEl.GetComputedCSSLength(fcaMarginRight,false);
-  ChildMarginTop:=ChildEl.GetComputedCSSLength(fcaMarginTop,false);
-  ChildMarginBottom:=ChildEl.GetComputedCSSLength(fcaMarginBottom,false);
-
-  ChildBorderLeft:=ChildEl.GetComputedCSSBorderWidth(fcaBorderLeftWidth);
-  ChildBorderRight:=ChildEl.GetComputedCSSBorderWidth(fcaBorderRightWidth);
-  ChildBorderTop:=ChildEl.GetComputedCSSBorderWidth(fcaBorderTopWidth);
-  ChildBorderBottom:=ChildEl.GetComputedCSSBorderWidth(fcaBorderBottomWidth);
-
-  ChildPaddingLeft:=ChildEl.GetComputedCSSLength(fcaPaddingLeft,false);
-  ChildPaddingRight:=ChildEl.GetComputedCSSLength(fcaPaddingRight,false);
-  ChildPaddingTop:=ChildEl.GetComputedCSSLength(fcaPaddingTop,false);
-  ChildPaddingBottom:=ChildEl.GetComputedCSSLength(fcaPaddingBottom,false);
+  ChildLeft:=ChildEl.GetComputedLength(fcaLeft,true);
+  ChildRight:=ChildEl.GetComputedLength(fcaRight,true);
+  ChildTop:=ChildEl.GetComputedLength(fcaTop,true);
+  ChildBottom:=ChildEl.GetComputedLength(fcaBottom,true);
+
+  //if ChildEl.Name='SliderPointDiv' then
+  //  writeln('TFLBlockFormattingContext.PlaceAbsoluteNode ',ChildEl.GetPath,' Left=',FloatToCSSStr(ChildLeft),',Top=',FloatToCSSStr(ChildTop),',Right=',FloatToCSSStr(ChildRight),',Bottom=',FloatToCSSStr(ChildBottom));
+
+  ChildMarginLeft:=ChildEl.GetComputedLength(fcaMarginLeft);
+  ChildMarginRight:=ChildEl.GetComputedLength(fcaMarginRight);
+  ChildMarginTop:=ChildEl.GetComputedLength(fcaMarginTop);
+  ChildMarginBottom:=ChildEl.GetComputedLength(fcaMarginBottom);
+
+  ChildBorderLeft:=ChildEl.GetComputedBorderWidth(fcaBorderLeftWidth);
+  ChildBorderRight:=ChildEl.GetComputedBorderWidth(fcaBorderRightWidth);
+  ChildBorderTop:=ChildEl.GetComputedBorderWidth(fcaBorderTopWidth);
+  ChildBorderBottom:=ChildEl.GetComputedBorderWidth(fcaBorderBottomWidth);
+
+  ChildPaddingLeft:=ChildEl.GetComputedLength(fcaPaddingLeft);
+  ChildPaddingRight:=ChildEl.GetComputedLength(fcaPaddingRight);
+  ChildPaddingTop:=ChildEl.GetComputedLength(fcaPaddingTop);
+  ChildPaddingBottom:=ChildEl.GetComputedLength(fcaPaddingBottom);
 
   ChildPadBorderX:=ChildBorderLeft+ChildPaddingLeft+ChildPaddingRight+ChildBorderRight;
   ChildPadBorderY:=ChildBorderTop+ChildPaddingTop+ChildPaddingBottom+ChildBorderBottom;
 
   // width, height are contentbox
-  ChildBorderBoxWidth:=ChildEl.GetComputedCSSLength(fcaWidth,false,true);
+  ChildBorderBoxWidth:=ChildEl.GetComputedLength(fcaWidth,true);
   if not IsNan(ChildBorderBoxWidth) then
     ChildBorderBoxWidth:=ChildBorderBoxWidth+ChildPadBorderX;
-  ChildBorderBoxHeight:=ChildEl.GetComputedCSSLength(fcaHeight,false,true);
+  ChildBorderBoxHeight:=ChildEl.GetComputedLength(fcaHeight,true);
   if not IsNan(ChildBorderBoxHeight) then
     ChildBorderBoxHeight:=ChildBorderBoxHeight+ChildPadBorderY;
 
@@ -500,19 +500,19 @@ begin
                 +ChildMarginBottom;
   end;
 
-  ChildEl.CSSComputedAttribute[fcaLeft]:=FloatToCSSStr(ChildLeft);
-  ChildEl.CSSComputedAttribute[fcaRight]:=FloatToCSSStr(ChildRight);
-  ChildEl.CSSComputedAttribute[fcaTop]:=FloatToCSSStr(ChildTop);
-  ChildEl.CSSComputedAttribute[fcaBottom]:=FloatToCSSStr(ChildBottom);
-  ChildEl.CSSComputedAttribute[fcaWidth]:=FloatToCSSStr(ChildBorderBoxWidth-ChildPadBorderX);
-  ChildEl.CSSComputedAttribute[fcaHeight]:=FloatToCSSStr(ChildBorderBoxHeight-ChildPadBorderY);
+  ChildEl.ComputedAttribute[fcaLeft]:=FloatToCSSPx(ChildLeft);
+  ChildEl.ComputedAttribute[fcaRight]:=FloatToCSSPx(ChildRight);
+  ChildEl.ComputedAttribute[fcaTop]:=FloatToCSSPx(ChildTop);
+  ChildEl.ComputedAttribute[fcaBottom]:=FloatToCSSPx(ChildBottom);
+  ChildEl.ComputedAttribute[fcaWidth]:=FloatToCSSPx(ChildBorderBoxWidth-ChildPadBorderX);
+  ChildEl.ComputedAttribute[fcaHeight]:=FloatToCSSPx(ChildBorderBoxHeight-ChildPadBorderY);
   //writeln('TFLBlockFormattingContext.PlaceAbsoluteNode ',ChildEl.GetPath,
-  //  ' Left=',ChildEl.CSSComputedAttribute[fcaLeft],
-  //  ' Top=',ChildEl.CSSComputedAttribute[fcaTop],
-  //  ' Right=',ChildEl.CSSComputedAttribute[fcaRight],
-  //  ' Bottom=',ChildEl.CSSComputedAttribute[fcaBottom],
-  //  ' Width=',ChildEl.CSSComputedAttribute[fcaWidth],
-  //  ' Height=',ChildEl.CSSComputedAttribute[fcaHeight],
+  //  ' Left=',ChildEl.ComputedAttribute[fcaLeft],
+  //  ' Top=',ChildEl.ComputedAttribute[fcaTop],
+  //  ' Right=',ChildEl.ComputedAttribute[fcaRight],
+  //  ' Bottom=',ChildEl.ComputedAttribute[fcaBottom],
+  //  ' Width=',ChildEl.ComputedAttribute[fcaWidth],
+  //  ' Height=',ChildEl.ComputedAttribute[fcaHeight],
   //  '');
 end;
 
@@ -565,9 +565,9 @@ begin
     ChildNode:=TSimpleFresnelLayoutNode(Node.Nodes[i]);
     ChildMinWidth:=ChildNode.GetMinWidthBorderBox;
     ChildEl:=ChildNode.Element;
-    Margin:=Max(0,ChildEl.GetComputedCSSLength(fcaMarginLeft,false));
+    Margin:=Max(0,ChildEl.GetComputedLength(fcaMarginLeft));
     ChildMinWidth:=ChildMinWidth+Margin;
-    Margin:=Max(0,ChildEl.GetComputedCSSLength(fcaMarginRight,false));
+    Margin:=Max(0,ChildEl.GetComputedLength(fcaMarginRight));
     ChildMinWidth:=ChildMinWidth+Margin;
     if MaxChildMinWidth<ChildMinWidth then
       MaxChildMinWidth:=ChildMinWidth;
@@ -587,7 +587,7 @@ var
 begin
   //Node.Element.WriteComputedAttributes('TFLBlockFormattingContext.Apply');
 
-  MaxContentWidth:=Node.Element.GetComputedCSSLength(fcaWidth,false);
+  MaxContentWidth:=Node.Element.GetComputedLength(fcaWidth);
   ComputeLayoutBorderBox(MaxContentWidth,true);
 end;
 
@@ -667,10 +667,10 @@ begin
   begin
     ChildNode:=TSimpleFresnelLayoutNode(Node.Nodes[NodeIndex]);
     ChildEl:=ChildNode.Element;
-    //debugln(['TFLBlockFormattingContext.ComputeLayoutBorderBox ',ChildEl.Name]);
+    //writeln('TFLBlockFormattingContext.ComputeLayoutBorderBox ',ChildEl.GetPath);
 
     // skip position: absolute and fixed
-    aPosition:=ChildEl.CSSComputedAttribute[fcaPosition];
+    aPosition:=ChildEl.ComputedAttribute[fcaPosition];
     case aPosition of
     '','static','relative','sticky': ;
     else
@@ -681,7 +681,7 @@ begin
     end;
 
     // display-outside inline or block
-    aDisplayOutside:=ChildEl.CSSComputedAttribute[fcaDisplayOutside];
+    aDisplayOutside:=ChildEl.ComputedAttribute[fcaDisplayOutside];
     case aDisplayOutside of
     '',
     'inline': IsInline:=true;
@@ -695,20 +695,20 @@ begin
       end;
     end;
 
-    ChildMarginLeft:=ChildEl.GetComputedCSSLength(fcaMarginLeft,false);
-    ChildMarginRight:=ChildEl.GetComputedCSSLength(fcaMarginRight,false);
-    ChildMarginTop:=ChildEl.GetComputedCSSLength(fcaMarginTop,false);
-    ChildMarginBottom:=ChildEl.GetComputedCSSLength(fcaMarginBottom,false);
+    ChildMarginLeft:=ChildEl.GetComputedLength(fcaMarginLeft);
+    ChildMarginRight:=ChildEl.GetComputedLength(fcaMarginRight);
+    ChildMarginTop:=ChildEl.GetComputedLength(fcaMarginTop);
+    ChildMarginBottom:=ChildEl.GetComputedLength(fcaMarginBottom);
 
-    ChildBorderLeft:=ChildEl.GetComputedCSSBorderWidth(fcaBorderLeftWidth);
-    ChildBorderRight:=ChildEl.GetComputedCSSBorderWidth(fcaBorderRightWidth);
-    ChildBorderTop:=ChildEl.GetComputedCSSBorderWidth(fcaBorderTopWidth);
-    ChildBorderBottom:=ChildEl.GetComputedCSSBorderWidth(fcaBorderBottomWidth);
+    ChildBorderLeft:=ChildEl.GetComputedBorderWidth(fcaBorderLeftWidth);
+    ChildBorderRight:=ChildEl.GetComputedBorderWidth(fcaBorderRightWidth);
+    ChildBorderTop:=ChildEl.GetComputedBorderWidth(fcaBorderTopWidth);
+    ChildBorderBottom:=ChildEl.GetComputedBorderWidth(fcaBorderBottomWidth);
 
-    ChildPaddingLeft:=ChildEl.GetComputedCSSLength(fcaPaddingLeft,false);
-    ChildPaddingRight:=ChildEl.GetComputedCSSLength(fcaPaddingRight,false);
-    ChildPaddingTop:=ChildEl.GetComputedCSSLength(fcaPaddingTop,false);
-    ChildPaddingBottom:=ChildEl.GetComputedCSSLength(fcaPaddingBottom,false);
+    ChildPaddingLeft:=ChildEl.GetComputedLength(fcaPaddingLeft);
+    ChildPaddingRight:=ChildEl.GetComputedLength(fcaPaddingRight);
+    ChildPaddingTop:=ChildEl.GetComputedLength(fcaPaddingTop);
+    ChildPaddingBottom:=ChildEl.GetComputedLength(fcaPaddingBottom);
 
     ChildPadBorderX:=ChildBorderLeft+ChildPaddingLeft+ChildPaddingRight+ChildBorderRight;
     ChildPadBorderY:=ChildBorderTop+ChildPaddingTop+ChildPaddingBottom+ChildBorderBottom;
@@ -718,12 +718,12 @@ begin
     //writeln('TFLBlockFormattingContext.ComputeLayoutBorderBox ',ChildEl.GetPath,' Margin=',FloatToStr(ChildMarginLeft),',',FloatToStr(ChildMarginTop),',',FloatToStr(ChildMarginRight),',',FloatToStr(ChildMarginBottom),' Border=',FloatToStr(ChildBorderLeft),',',FloatToStr(ChildBorderTop),',',FloatToStr(ChildBorderRight),',',FloatToStr(ChildBorderBottom),' Padding=',FloatToStr(ChildPaddingLeft),',',FloatToStr(ChildPaddingTop),',',FloatToStr(ChildPaddingRight),',',FloatToStr(ChildPaddingBottom));
 
     // width, height are contentbox and can be NaN
-    ChildWidth:=ChildEl.GetComputedCSSLength(fcaWidth,false,true);
-    ChildHeight:=ChildEl.GetComputedCSSLength(fcaHeight,false,true);
-    ChildMinWidth:=ChildEl.GetComputedCSSLength(fcaMinWidth,false,true);
-    ChildMaxWidth:=ChildEl.GetComputedCSSLength(fcaMaxWidth,false,true);
-    ChildMinHeight:=ChildEl.GetComputedCSSLength(fcaMinHeight,false,true);
-    ChildMaxHeight:=ChildEl.GetComputedCSSLength(fcaMaxHeight,false,true);
+    ChildWidth:=ChildEl.GetComputedLength(fcaWidth,true);
+    ChildHeight:=ChildEl.GetComputedLength(fcaHeight,true);
+    ChildMinWidth:=ChildEl.GetComputedLength(fcaMinWidth,true);
+    ChildMaxWidth:=ChildEl.GetComputedLength(fcaMaxWidth,true);
+    ChildMinHeight:=ChildEl.GetComputedLength(fcaMinHeight,true);
+    ChildMaxHeight:=ChildEl.GetComputedLength(fcaMaxHeight,true);
 
     //writeln('TFLBlockFormattingContext.ComputeLayoutBorderBox ',ChildEl.GetPath,' Commit=',Commit,' ChildWidth=',FloatToStr(ChildWidth),' ChildHeight=',FloatToStr(ChildHeight));
 
@@ -900,27 +900,27 @@ var
 
   function GetLimit(Attr: TFresnelCSSAttribute; out aLimit: TFresnelLength): boolean;
   begin
-    aLimit:=Element.GetComputedCSSLength(Attr,false,true);
+    aLimit:=Element.GetComputedLength(Attr,true);
     Result:=not IsNan(aLimit);
   end;
 
   function GetExtraWidth: TFresnelLength;
   begin
     if ExtraWidth<0 then
-      ExtraWidth:=Element.GetComputedCSSLength(fcaBorderLeft,false)
-                 +Element.GetComputedCSSLength(fcaBorderRight,false)
-                 +Element.GetComputedCSSLength(fcaPaddingLeft,false)
-                 +Element.GetComputedCSSLength(fcaPaddingRight,false);
+      ExtraWidth:=Element.GetComputedLength(fcaBorderLeft)
+                 +Element.GetComputedLength(fcaBorderRight)
+                 +Element.GetComputedLength(fcaPaddingLeft)
+                 +Element.GetComputedLength(fcaPaddingRight);
     Result:=ExtraWidth;
   end;
 
   function GetExtraHeight: TFresnelLength;
   begin
     if ExtraHeight<0 then
-      ExtraHeight:=Element.GetComputedCSSLength(fcaBorderTop,false)
-                  +Element.GetComputedCSSLength(fcaBorderBottom,false)
-                  +Element.GetComputedCSSLength(fcaPaddingTop,false)
-                  +Element.GetComputedCSSLength(fcaPaddingBottom,false);
+      ExtraHeight:=Element.GetComputedLength(fcaBorderTop)
+                  +Element.GetComputedLength(fcaBorderBottom)
+                  +Element.GetComputedLength(fcaPaddingTop)
+                  +Element.GetComputedLength(fcaPaddingBottom);
     Result:=ExtraHeight;
   end;
 
@@ -1025,16 +1025,16 @@ begin
   if Viewport.NodeCount=0 then
     exit; // nothing to do
 
-  aDisplayBox:=Viewport.CSSComputedAttribute[fcaDisplayBox];
-  if aDisplayBox<>'' then
-    ErrorLayout(20221031184139,'TFresnelLayouter.Apply expected viewport.displaybox=, but found "'+aDisplayBox+'"');
-  aDisplayOutside:=Viewport.CSSComputedAttribute[fcaDisplayOutside];
+  aDisplayBox:=Viewport.ComputedAttribute[fcaDisplayBox];
+  if aDisplayBox<>'contents' then
+    ErrorLayout(20221031184139,'TFresnelLayouter.Apply expected viewport.displaybox=contents, but found "'+aDisplayBox+'"');
+  aDisplayOutside:=Viewport.ComputedAttribute[fcaDisplayOutside];
   if aDisplayOutside<>'block' then
     ErrorLayout(20221031182815,'TFresnelLayouter.Apply expected viewport.displayoutside=block, but found "'+aDisplayOutside+'"');
-  aDisplayInside:=Viewport.CSSComputedAttribute[fcaDisplayInside];
+  aDisplayInside:=Viewport.ComputedAttribute[fcaDisplayInside];
   if aDisplayInside<>'flow-root' then
     ErrorLayout(20221018142350,'TFresnelLayouter.Apply expected viewport.displayinside=flow-root, but found "'+aDisplayInside+'"');
-  aPosition:=Viewport.CSSComputedAttribute[fcaPosition];
+  aPosition:=Viewport.ComputedAttribute[fcaPosition];
   if aPosition<>'absolute' then
     ErrorLayout(20221031153911,'TFresnelLayouter.Apply expected viewport.position=absolute, but found "'+aPosition+'"');
 
@@ -1055,22 +1055,22 @@ begin
   if El.Parent=nil then
     exit(true);
 
-  aPosition:=El.CSSComputedAttribute[fcaPosition];
+  aPosition:=El.ComputedAttribute[fcaPosition];
   case aPosition of
   'absolute',
   'fixed': exit(true);
   end;
 
-  aDisplayBox:=El.CSSComputedAttribute[fcaDisplayBox];
+  aDisplayBox:=El.ComputedAttribute[fcaDisplayBox];
   if aDisplayBox='none' then
     exit(false);
 
-  aDisplayOutside:=El.CSSComputedAttribute[fcaDisplayOutside];
+  aDisplayOutside:=El.ComputedAttribute[fcaDisplayOutside];
   case aDisplayOutside of
   'block': exit(true);
   end;
 
-  aDisplayInside:=El.CSSComputedAttribute[fcaDisplayInside];
+  aDisplayInside:=El.ComputedAttribute[fcaDisplayInside];
   case aDisplayInside of
   'flow-root',
   'table',
@@ -1079,13 +1079,13 @@ begin
   'ruby': exit(true);
   end;
 
-  aOverflow:=El.CSSComputedAttribute[fcaOverflow];
+  aOverflow:=El.ComputedAttribute[fcaOverflow];
   case aOverflow of
   'visible',
   'clip': exit(true);
   end;
 
-  aFloat:=El.CSSComputedAttribute[fcaFloat];
+  aFloat:=El.ComputedAttribute[fcaFloat];
   case aFloat of
   '', 'none': ;
   else
@@ -1105,13 +1105,8 @@ function TViewportLayouter.GetBlockContainer(El: TFresnelElement
   ): TFresnelElement;
 var
   aValue, aPosition: String;
-  Node: TSimpleFresnelLayoutNode;
 begin
-  Node:=TSimpleFresnelLayoutNode(El.LayoutNode);
-  Result:=Node.BlockContainer;
-  if Result<>nil then exit;
-
-  aPosition:=El.CSSComputedAttribute[fcaPosition];
+  aPosition:=El.ComputedAttribute[fcaPosition];
   case aPosition of
   'absolute':
     begin
@@ -1119,7 +1114,7 @@ begin
       Result:=El.Parent;
       while (Result<>nil) do
       begin
-        aValue:=Result.CSSComputedAttribute[fcaPosition];
+        aValue:=Result.ComputedAttribute[fcaPosition];
         if (aValue<>'static') and (aValue<>'') then
           exit;
         // Note:check for transform<>'none'
@@ -1158,7 +1153,7 @@ begin
   LayouterClass:=nil;
   if (not LNode.SkipLayout) and (El.NodeCount>0) then
   begin
-    aDisplayInside:=El.CSSComputedAttribute[fcaDisplayInside];
+    aDisplayInside:=El.ComputedAttribute[fcaDisplayInside];
     if aDisplayInside='grid' then
       LayouterClass:=TFLGridLayouter
     else if aDisplayInside='flex' then
@@ -1193,11 +1188,11 @@ begin
   if LNode.SkipLayout or (El.Parent=nil) then
     LNode.Parent:=nil
   else begin
-    aPosition:=El.CSSComputedAttribute[fcaPosition];
+    aPosition:=El.ComputedAttribute[fcaPosition];
     case aPosition of
     'absolute','fixed','relative','sticky':
       begin
-        ZIndexStr:=El.CSSComputedAttribute[fcaZIndex];
+        ZIndexStr:=El.ComputedAttribute[fcaZIndex];
         if (ZIndexStr='') or (ZIndexStr='auto') then
           LNode.ZIndex:=0.0
         else begin
@@ -1205,7 +1200,7 @@ begin
           val(ZIndexStr,ZIndexInt,Code);
           LNode.ZIndex:=ZIndexInt;
         end;
-        LNode.ZIndex:=LNode.ZIndex+0.1;
+        LNode.ZIndex:=LNode.ZIndex+0.1; // slightly higher than no position
       end;
     else
       LNode.ZIndex:=0;
@@ -1225,81 +1220,6 @@ begin
   end;
 end;
 
-function TViewportLayouter.GetPixPerUnit(El: TFresnelElement;
-  anUnit: TFresnelCSSUnit; IsHorizontal: boolean): TFresnelLength;
-var
-  aContainer: TFresnelElement;
-begin
-  Result:=1;
-  case anUnit of
-    fcuPercent:
-      begin
-        aContainer:=GetBlockContainer(El);
-        //writeln('TViewportLayouter.GetPixPerUnit ',El.GetPath,' ',aContainer<>nil,' ',IsHorizontal);
-        if aContainer<>nil then
-        begin
-          if IsHorizontal then
-          begin
-            Result:=aContainer.GetComputedCSSLength(fcaWidth,false);
-            //writeln('TViewportLayouter.GetPixPerUnit ',El.GetPath,' Container=',aContainer.GetPath,' ',IsHorizontal,' Con.Width=',Result);
-          end
-          else begin
-            Result:=aContainer.GetComputedCSSLength(fcaHeight,false);
-            //writeln('TViewportLayouter.GetPixPerUnit ',El.GetPath,' Container=',aContainer.GetPath,' ',IsHorizontal,' Con.Height=',Result);
-          end;
-        end else begin
-          if IsHorizontal then
-            Result:=Viewport.Width
-          else
-            Result:=Viewport.Height;
-        end;
-        Result:=Result/100;
-      end;
-    fcu_cm: Result:=Viewport.DPI[IsHorizontal]/2.54;
-    fcu_mm: Result:=Viewport.DPI[IsHorizontal]/25.4;
-    fcu_ch,
-    fcu_em,
-    fcu_ex:
-      begin
-        // relative to font of element
-        Result:=El.GetComputedCSSLength(fcaFontSize,true);
-        // ToDo
-        case anUnit of
-        fcu_ch: Result:=Result/2;
-        fcu_em: ;
-        fcu_ex: ;
-        end;
-      end;
-    fcu_in: Result:=Viewport.DPI[IsHorizontal];
-    fcu_px: Result:=1.0;
-    fcu_pt: Result:=Viewport.DPI[IsHorizontal]/72; // 1pt = 1/72 of 1in
-    fcu_pc: Result:=Viewport.DPI[IsHorizontal]/6; // 1pc = 12 pt
-    fcu_rem:
-      // relative to font-size of the root element
-      Result:=GetPixPerUnit(Viewport,fcu_em,IsHorizontal);
-    fcu_vw,
-    fcu_vh,
-    fcu_vmax,
-    fcu_vmin:
-      begin
-        case anUnit of
-        fcu_vw: Result:=Viewport.Width/100; // relative to 1% of the width of the viewport
-        fcu_vh: Result:=Viewport.Height/100; // relative to 1% of the height of the viewport
-        fcu_vmax: // relative to 1% of viewport's larger dimension
-          if Viewport.Width>Viewport.Height then
-            Result:=Viewport.Width/100
-          else
-            Result:=Viewport.Height/100;
-        fcu_vmin: // relative to 1% of viewport's smaller dimension
-          if Viewport.Width>Viewport.Height then
-            Result:=Viewport.Height/100
-          else
-            Result:=Viewport.Width/100;
-        end;
-      end;
-  end;
-end;
-
 procedure TViewportLayouter.ComputeCSSLayoutNode(El: TFresnelElement);
 var
   LNode, ParentLNode: TSimpleFresnelLayoutNode;
@@ -1323,7 +1243,7 @@ begin
     ParentLNode:=nil;
 
   // display-box
-  aDisplayBox:=El.CSSComputedAttribute[fcaDisplayBox];
+  aDisplayBox:=El.ComputedAttribute[fcaDisplayBox];
   if aDisplayBox='none' then
   begin
     LNode.SkipLayout:=true;
@@ -1334,7 +1254,7 @@ begin
   UpdateLayouter(El,LNode);
 
   // visibility
-  aVisibility:=El.CSSComputedAttribute[fcaVisibility];
+  aVisibility:=El.ComputedAttribute[fcaVisibility];
   case aVisibility of
   'hidden','collapse':
     LNode.SkipRendering:=true;
@@ -1405,7 +1325,7 @@ procedure TViewportLayouter.WriteLayoutTree;
     El: TFresnelElement;
   begin
     El:=Node.Element;
-    FLLog(etError,[El.Name,' NodeCount=',IntToStr(Node.NodeCount),' display-outside=',El.CSSComputedAttribute[fcaDisplayOutside],' display-inside=',El.CSSComputedAttribute[fcaDisplayInside],' position=',El.CSSComputedAttribute[fcaPosition],' z-index=',El.CSSComputedAttribute[fcaZIndex]]);
+    FLLog(etError,[El.Name,' NodeCount=',IntToStr(Node.NodeCount),' display-outside=',El.ComputedAttribute[fcaDisplayOutside],' display-inside=',El.ComputedAttribute[fcaDisplayInside],' position=',El.ComputedAttribute[fcaPosition],' z-index=',El.ComputedAttribute[fcaZIndex]]);
     if Node.Layouter<>nil then
       FLLog(etDebug,[' layouter=',Node.Layouter.ClassName]);
     FLLog(etDebug,'');

+ 22 - 49
src/base/fresnel.renderer.pas

@@ -74,8 +74,6 @@ type
     procedure DrawElement(El: TFresnelElement); virtual;
     // Draw the children of the element
     procedure DrawChildren(El: TFresnelElement); virtual;
-    // Call UpdateRenderedAttributes on an element and all its children.
-    procedure UpdateRenderedAttributes(El: TFresnelElement); virtual;
     // Set the origin of the currently drawn element.
     procedure SetOrigin(const AValue: TFresnelPoint); virtual;
     // Get the origin of the currently drawn element.
@@ -490,7 +488,6 @@ end;
 procedure TFresnelRenderer.DrawElement(El: TFresnelElement);
 var
   LNode: TSimpleFresnelLayoutNode;
-  aBackgroundColor, aBorderColor: String;
   aLeft, aTop, aRight, aBottom,
     aMarginLeft, aMarginTop, aMarginRight, aMarginBottom,
     aBorderLeft, aBorderRight, aBorderTop, aBorderBottom,
@@ -508,26 +505,26 @@ begin
   aRenderable:=El as IFresnelRenderable;
   aRenderable.BeforeRender;
   El.Rendered:=true;
-  aLeft:=El.GetRenderedCSSLength(fcaLeft,false);
-  aTop:=El.GetRenderedCSSLength(fcaTop,false);
-  aRight:=El.GetRenderedCSSLength(fcaRight,false);
-  aBottom:=El.GetRenderedCSSLength(fcaBottom,false);
+  aLeft:=El.GetComputedLength(fcaLeft);
+  aTop:=El.GetComputedLength(fcaTop);
+  aRight:=El.GetComputedLength(fcaRight);
+  aBottom:=El.GetComputedLength(fcaBottom);
   FLLog(etDebug,'TFresnelRenderer.DrawElement %s [(%gx%g),(%gx%g)]',[El.GetPath,aLeft,aTop,aright,aBottom]);
 
-  aMarginLeft:=El.GetRenderedCSSLength(fcaMarginLeft,false);
-  aMarginRight:=El.GetRenderedCSSLength(fcaMarginRight,false);
-  aMarginTop:=El.GetRenderedCSSLength(fcaMarginTop,false);
-  aMarginBottom:=El.GetRenderedCSSLength(fcaMarginBottom,false);
+  aMarginLeft:=El.GetComputedLength(fcaMarginLeft);
+  aMarginRight:=El.GetComputedLength(fcaMarginRight);
+  aMarginTop:=El.GetComputedLength(fcaMarginTop);
+  aMarginBottom:=El.GetComputedLength(fcaMarginBottom);
 
-  aBorderLeft:=El.GetRenderedCSSBorderWidth(fcaBorderLeftWidth);
-  aBorderRight:=El.GetRenderedCSSBorderWidth(fcaBorderRightWidth);
-  aBorderTop:=El.GetRenderedCSSBorderWidth(fcaBorderTopWidth);
-  aBorderBottom:=El.GetRenderedCSSBorderWidth(fcaBorderBottomWidth);
+  aBorderLeft:=El.GetComputedBorderWidth(fcaBorderLeftWidth);
+  aBorderRight:=El.GetComputedBorderWidth(fcaBorderRightWidth);
+  aBorderTop:=El.GetComputedBorderWidth(fcaBorderTopWidth);
+  aBorderBottom:=El.GetComputedBorderWidth(fcaBorderBottomWidth);
 
-  aPaddingLeft:=El.GetRenderedCSSLength(fcaPaddingLeft,false);
-  aPaddingRight:=El.GetRenderedCSSLength(fcaPaddingRight,false);
-  aPaddingTop:=El.GetRenderedCSSLength(fcaPaddingTop,false);
-  aPaddingBottom:=El.GetRenderedCSSLength(fcaPaddingBottom,false);
+  aPaddingLeft:=El.GetComputedLength(fcaPaddingLeft);
+  aPaddingRight:=El.GetComputedLength(fcaPaddingRight);
+  aPaddingTop:=El.GetComputedLength(fcaPaddingTop);
+  aPaddingBottom:=El.GetComputedLength(fcaPaddingBottom);
 
   aBorderBox.Left:=aLeft+aMarginLeft;
   aBorderBox.Top:=aTop+aMarginTop;
@@ -556,24 +553,18 @@ begin
     BorderParams.Width[ffsBottom]:=aBorderBottom;
 
     // background-color
-    aBackgroundColor:=El.CSSRenderedAttribute[fcaBackgroundColor];
-    if not CSSToFPColor(aBackgroundColor,BorderParams.BackgroundColorFP) then
-      BorderParams.BackgroundColorFP:=colTransparent;
+    BorderParams.BackgroundColorFP:=El.GetComputedColor(fcaBackgroundColor,colTransparent);
 
     // border-color
     for s in TFresnelCSSSide do
-    begin
-      aBorderColor:=El.CSSRenderedAttribute[TFresnelCSSAttribute(ord(fcaBorderLeftColor)+ord(s))];
-      if not CSSToFPColor(aBorderColor,BorderParams.Color[s]) then
-        BorderParams.Color[s]:=colTransparent;
-    end;
+      BorderParams.Color[s]:=El.GetComputedColor(TFresnelCSSAttribute(ord(fcaBorderLeftColor)+ord(s)),colTransparent);
 
     // border-image
-    BorderParams.BackgroundImage:=El.GetRenderedCSSImage(fcaBackgroundImage);
+    BorderParams.BackgroundImage:=El.GetComputedImage(fcaBackgroundImage);
 
     // border-radius
     for Corner in TFresnelCSSCorner do
-      BorderParams.BoundingBox.Radii[Corner]:=El.GetRenderedCSSBorderRadius(Corner);
+      BorderParams.BoundingBox.Radii[Corner]:=El.GetComputedBorderRadius(Corner);
     // Normalize
     if PrepareBackgroundBorder(El,BorderParams) then
       begin
@@ -586,7 +577,7 @@ begin
     BorderParams.Free;
   end;
 
-  // Give element a chance to draw itself.
+  // Give element a chance to draw itself
   aRenderable.Render(Self as IFresnelRenderer);
 
   DrawChildren(El);
@@ -615,28 +606,12 @@ begin
   Origin:=OldOrigin;
 end;
 
-procedure TFresnelRenderer.UpdateRenderedAttributes(El: TFresnelElement);
-var
-  LNode: TSimpleFresnelLayoutNode;
-  i: Integer;
-begin
-  LNode:=TSimpleFresnelLayoutNode(El.LayoutNode);
-  if LNode.SkipRendering then exit;
-
-  El.UpdateRenderedAttributes;
-  for i:=0 to LNode.NodeCount-1 do
-    UpdateRenderedAttributes(TSimpleFresnelLayoutNode(LNode.Nodes[i]).Element);
-end;
-
 procedure TFresnelRenderer.Draw(Viewport: TFresnelViewport);
 var
   aContentBox: TFresnelRect;
-  aBackgroundColor: String;
   BackgroundColorFP: TFPColor;
 begin
   //debugln(['TFresnelRenderer.Draw Origin=',dbgs(Origin)]);
-  UpdateRenderedAttributes(Viewport);
-
   Viewport.Rendered:=true;
   aContentBox.Left:=0;
   aContentBox.Top:=0;
@@ -645,9 +620,7 @@ begin
   Viewport.RenderedBorderBox:=aContentBox;
   Viewport.RenderedContentBox:=aContentBox;
 
-  aBackgroundColor:=Viewport.CSSRenderedAttribute[fcaBackgroundColor];
-  if not CSSToFPColor(aBackgroundColor,BackgroundColorFP) then
-    BackgroundColorFP:=colWhite;
+  BackgroundColorFP:=Viewport.GetComputedColor(fcaBackgroundColor,colWhite);
 
   FillRect(BackgroundColorFP,aContentBox);
 

+ 37 - 19
src/base/fresnel.textlayouter.pas

@@ -3,13 +3,17 @@ unit Fresnel.TextLayouter;
 {$mode objfpc}
 {$modeswitch advancedrecords}
 
+{$IF FPC_FULLVERSION>30300}
+  {$DEFINE HasTObjectToString}
+{$ENDIF}
+
 interface
 
 uses
 {$IFDEF FPC_DOTTEDUNITS}
   System.Classes, System.SysUtils, System.Types, System.Contnrs, fpImage, System.UITypes;
 {$ELSE}
-  Classes, SysUtils, Types, Contnrs, fpImage, System.UITypes;
+  Classes, SysUtils, Types, Fresnel.Classes, Contnrs, fpImage, System.UITypes;
 {$ENDIF}
 
 Const
@@ -36,7 +40,7 @@ Type
   TOverlappingRangesAction = (oraError,oraFit);
   TCullThreshold = 1..100;
 
-  TTextUnits = single;
+  TTextUnits = TFresnelLength;
 
   { No hyphenation:
 
@@ -76,7 +80,7 @@ Type
     Width, Height : TTextUnits;
     Ascender, Descender : TTextUnits;
   end;
-  TTextPoint = {$IFDEF FPC_DOTTEDUNITS}System.{$ENDIF}Types.TPointF;
+  TTextPoint = TFresnelPoint;
 
   TFontAttribute = (faBold,faItalic,faUnderline,faStrikeOut);
   TFontAttributes = set of TFontAttribute;
@@ -200,7 +204,9 @@ Type
     // At pos is relative to the text here, zero based
     function Split(atPos : integer) : TTextBlock; virtual;
     procedure Assign(aBlock : TTextBlock); virtual;
+    {$IFDEF HasTObjectToString}
     function ToString : RTLString; override;
+    {$ENDIF}
     Procedure TrimTrailingWhiteSpace;
     // Text for this block. Calculated from offset/len
     Property Text : TTextString Read GetText;
@@ -246,7 +252,9 @@ Type
     destructor destroy; override;
     procedure Assign(Source: TPersistent); override;
     Procedure Changed;
+    {$IFDEF HasTObjectToString}
     function ToString : RTLString; override;
+    {$ENDIF}
   Published
     // Offset is 0 based and is the offset from the first character in the text.
     Property CharOffset : SizeInt Read FCharOffset Write SetCharOffSet;
@@ -276,9 +284,9 @@ Type
     FWidth: TTextUnits;
     FLayouter : TTextLayouter;
     function GetAsPoint: TTextPoint;
-    procedure SetAsPoint(AValue: TTextPoint);
-    procedure SetHeight(AValue: TTextUnits);
-    procedure SetWidth(AValue: TTextUnits);
+    procedure SetAsPoint(const AValue: TTextPoint);
+    procedure SetHeight(const AValue: TTextUnits);
+    procedure SetWidth(const AValue: TTextUnits);
   protected
     procedure Changed; virtual;
     function GetOwner: TPersistent; override;
@@ -374,7 +382,9 @@ Type
     Procedure Reset;
     // Check if ranges do not overlap.
     procedure CheckRanges;
+    {$IFDEF HasTObjectToString}
     function ToString : RTLString; override;
+    {$ENDIF}
     Property TextBlocks[aIndex : Integer] : TTextBlock Read GetBlock;
     Property TextBlockCount : Integer Read GetBlockCount;
     function Execute : integer; virtual;
@@ -385,8 +395,8 @@ Type
     Function GetMaxRight : TTextUnits;
     Function GetMinTop : TTextUnits;
     Function GetMaxBottom : TTextUnits;
-    Function GetTotalSize : TSizeF;
-    Function GetBoundsRect : TRectF;
+    Function GetTotalSize : TFresnelPoint;
+    Function GetBoundsRect : TFresnelRect;
     // Color of font
     Property FPColor : TFPColor Read GetColor Write SetColor;
   Published
@@ -487,7 +497,7 @@ begin
   Self.TextLen:=AtPos;
   // Reset formatting stuff on new
   Result.ForceNewLine:=False;
-  Result.LayoutPos:=Default(TPointF);
+  Result.LayoutPos:=Default(TTextPoint);
   Result.Size.Width:=0;
   Result.Size.Height:=0;
   Result.Size.Descender:=0;
@@ -507,11 +517,13 @@ begin
   ForceNewLine:=aBlock.ForceNewLine;
 end;
 
+{$IFDEF HasTObjectToString}
 function TTextBlock.ToString: RTLString;
 begin
   Result:=Inherited ToString;
   Result:=Result+Format(': (x: %g, y: %g, w: %g, h:%g) [Off: %d, len: %d]: >>%s<< ',[LayoutPos.X,LayoutPos.Y,Size.Width,Size.Height,TextOffset,TextLen,Text]);
 end;
+{$ENDIF}
 
 procedure TTextBlock.TrimTrailingWhiteSpace;
 
@@ -596,10 +608,12 @@ begin
     TTextLayouter(Collection.Owner).Reset;
 end;
 
+{$IFDEF HasTObjectToString}
 function TTextRange.ToString: RTLString;
 begin
   Result:=Format('[offset %d, len: %d]',[CharOffset,CharLength]);
 end;
+{$ENDIF}
 
 { TTextRangeList }
 
@@ -624,7 +638,7 @@ end;
 
 { TTextLayoutBounds }
 
-procedure TTextLayoutBounds.SetHeight(AValue: TTextUnits);
+procedure TTextLayoutBounds.SetHeight(const AValue: TTextUnits);
 begin
   if FHeight=AValue then Exit;
   FHeight:=AValue;
@@ -633,17 +647,18 @@ end;
 
 function TTextLayoutBounds.GetAsPoint: TTextPoint;
 begin
-  Result:=PointF(Width,Height);
+  Result.X:=Width;
+  Result.Y:=Height;
 end;
 
-procedure TTextLayoutBounds.SetAsPoint(AValue: TTextPoint);
+procedure TTextLayoutBounds.SetAsPoint(const AValue: TTextPoint);
 begin
   FWidth:=aValue.X;
   FHeight:=aValue.Y;
   Changed;
 end;
 
-procedure TTextLayoutBounds.SetWidth(AValue: TTextUnits);
+procedure TTextLayoutBounds.SetWidth(const AValue: TTextUnits);
 begin
   if FWidth=AValue then Exit;
   FWidth:=AValue;
@@ -1120,6 +1135,7 @@ begin
   FBlocks.Clear;
 end;
 
+{$IFDEF HasTObjectToString}
 function TTextLayouter.ToString: RTLString;
 var
   I : Integer;
@@ -1128,6 +1144,7 @@ begin
   For I:=0 to TextBlockCount-1 do
     Result:=Result+TextBlocks[I].ToString+sLineBreak;
 end;
+{$ENDIF}
 
 function TTextLayouter.AddBlock(aOffset,aLength : SizeInt; aFont : TTextFont) : TTextBlock;
 
@@ -1355,7 +1372,7 @@ var
 
 begin
   Result:=False;
-  CurrPos:=Pointf(0,0);
+  CurrPos:=Default(TTextPoint);
   I:=0;
   While I<FBlocks.Count do
     begin
@@ -1596,14 +1613,15 @@ begin
   Result:=yMax;
 end;
 
-function TTextLayouter.GetTotalSize: TSizeF;
+function TTextLayouter.GetTotalSize: TFresnelPoint;
 begin
-  Result:=TSizeF.Create(GetTotalWidth,GetTotalHeight);
+  Result.X:=GetTotalWidth;
+  Result.Y:=GetTotalHeight;
 end;
 
-function TTextLayouter.GetBoundsRect: TRectF;
+function TTextLayouter.GetBoundsRect: TFresnelRect;
 begin
-  Result:=TRectF.Create(GetMinLeft,GetMinTop,GetMaxRight,GetMaxBottom);
+  Result:=TFresnelRect.Create(GetMinLeft,GetMinTop,GetMaxRight,GetMaxBottom);
 end;
 
 procedure TTextLayouter.ApplyStretchMode(const ADesiredHeight: TTextUnits);
@@ -1640,7 +1658,7 @@ var
   i: integer;
   lBlock: TTextBlock;
   MaxHeight, vPos : TTextUnits;
-  lRemainingHeight: single;
+  lRemainingHeight: TFresnelLength;
   d: single;
   doDelete : Boolean;
   aSize : TTextMeasures;

+ 5 - 0
src/base/fresnelbase.lpk

@@ -110,6 +110,11 @@
         <Filename Value="fresnel.keys.pas"/>
         <UnitName Value="fresnel.keys"/>
       </Item>
+      <Item>
+        <Filename Value="fcl-css/fpcssresparser.pas"/>
+        <AddToUsesPkgSection Value="False"/>
+        <UnitName Value="fpCSSResParser"/>
+      </Item>
     </Files>
     <UsageOptions>
       <UnitPath Value="$(PkgOutDir)"/>

+ 3 - 2
src/base/fresnelbase.pas

@@ -8,8 +8,9 @@ unit FresnelBase;
 interface
 
 uses
-  Fresnel.Controls, Fresnel.DOM, Fresnel.Layouter, Fresnel.Renderer, FCL.Events, Fresnel.Events, Fresnel.Forms, Fresnel.WidgetSet, 
-  Fresnel.Resources, Fresnel.StrConsts, Fresnel.Classes, Fresnel.Images, UTF8Utils, Fresnel.AsyncCalls, Fresnel.TextLayouter;
+  Fresnel.Controls, Fresnel.DOM, Fresnel.Layouter, Fresnel.Renderer, FCL.Events, Fresnel.Events, 
+  Fresnel.Forms, Fresnel.WidgetSet, Fresnel.Resources, Fresnel.StrConsts, Fresnel.Classes, 
+  Fresnel.Images, UTF8Utils, Fresnel.AsyncCalls, Fresnel.TextLayouter, fresnel.keys;
 
 implementation
 

+ 3 - 0
src/base/utf8utils.pp

@@ -18,6 +18,9 @@ unit UTF8Utils;
 
 {$mode objfpc}{$H+}{$inline on}
 
+{$IF FPC_FULLVERSION>30300}
+{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
+{$ENDIF}
 
 interface
 

部分文件因为文件数量过多而无法显示