Browse Source

added fresnel package

mattias 2 years ago
parent
commit
fe34d05ec6
8 changed files with 5353 additions and 0 deletions
  1. 1 0
      src/.gitignore
  2. 53 0
      src/fresnel.lpk
  3. 22 0
      src/fresnel.pas
  4. 382 0
      src/fresnelcontrols.pas
  5. 2742 0
      src/fresneldom.pas
  6. 1473 0
      src/fresnellayouter.pas
  7. 532 0
      src/fresnellclcontrols.pas
  8. 148 0
      src/fresnelrenderer.pas

+ 1 - 0
src/.gitignore

@@ -0,0 +1 @@
+lib

+ 53 - 0
src/fresnel.lpk

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <Package Version="5">
+    <Name Value="Fresnel"/>
+    <Type Value="RunAndDesignTime"/>
+    <Author Value="Mattias Gaertner"/>
+    <CompilerOptions>
+      <Version Value="11"/>
+      <SearchPaths>
+        <OtherUnitFiles Value="$(FPCSrcDir)/packages/fcl-css/src"/>
+        <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)/"/>
+      </SearchPaths>
+    </CompilerOptions>
+    <Description Value="CSS components"/>
+    <License Value="modified LGPL-2
+"/>
+    <Version Minor="2"/>
+    <Files>
+      <Item>
+        <Filename Value="fresnelcontrols.pas"/>
+        <UnitName Value="FresnelControls"/>
+      </Item>
+      <Item>
+        <Filename Value="fresneldom.pas"/>
+        <UnitName Value="FresnelDOM"/>
+      </Item>
+      <Item>
+        <Filename Value="fresnellayouter.pas"/>
+        <UnitName Value="FresnelLayouter"/>
+      </Item>
+      <Item>
+        <Filename Value="fresnellclcontrols.pas"/>
+        <UnitName Value="FresnelLCLControls"/>
+      </Item>
+      <Item>
+        <Filename Value="fresnelrenderer.pas"/>
+        <UnitName Value="fresnelrenderer"/>
+      </Item>
+    </Files>
+    <RequiredPkgs>
+      <Item>
+        <PackageName Value="LCLBase"/>
+      </Item>
+    </RequiredPkgs>
+    <UsageOptions>
+      <UnitPath Value="$(PkgOutDir)"/>
+    </UsageOptions>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+  </Package>
+</CONFIG>

+ 22 - 0
src/fresnel.pas

@@ -0,0 +1,22 @@
+{ This file was automatically created by Lazarus. Do not edit!
+  This source is only used to compile and install the package.
+ }
+
+unit Fresnel;
+
+{$warn 5023 off : no warning about unused units}
+interface
+
+uses
+  FresnelControls, FresnelDOM, FresnelLayouter, FresnelLCLControls, 
+  FresnelRenderer, LazarusPackageIntf;
+
+implementation
+
+procedure Register;
+begin
+end;
+
+initialization
+  RegisterPackage('Fresnel', @Register);
+end.

+ 382 - 0
src/fresnelcontrols.pas

@@ -0,0 +1,382 @@
+unit FresnelControls;
+
+{$mode ObjFPC}{$H+}
+{$WARN 6060 off} // Case statement does not handle all possible cases
+
+interface
+
+uses
+  Classes, SysUtils, Math, FresnelDOM, LazLoggerBase, fpCSSResolver,
+  fpCSSTree;
+
+type
+
+  { TFresnelDiv }
+
+  TFresnelDiv = class(TFresnelElement)
+  private
+    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;
+  end;
+
+  { TFresnelSpan }
+
+  TFresnelSpan = class(TFresnelElement)
+  private
+    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;
+  end;
+
+  { TFresnelReplacedElement }
+
+  TFresnelReplacedElement = class(TFresnelElement)
+  public
+    procedure GetMinMaxPreferred(out MinWidth, MinHeight, PreferredWidth, PreferredHeight, MaxWidth, MaxHeight: TFresnelLength); virtual;
+  end;
+
+  TFresnelLabelState = (
+    flsMinCaptionValid,
+    flsMinWidthValid,
+    flsSizeValid
+    );
+  TFresnelLabelStates = set of TFresnelLabelState;
+
+  { TFresnelLabel }
+
+  TFresnelLabel = class(TFresnelReplacedElement)
+  private
+    class var FFresnelLabelTypeID: TCSSNumericalID;
+    class constructor InitFresnelLabelClass;
+  private
+    FCaption: String;
+  protected
+    FLabelStates: TFresnelLabelStates;
+    FMinCaption: String; // Caption with linebreak after each word
+    FMinWidth: TFresnelLength; // width of longest word of Caption
+    FSize: TFresnelPoint; // width+height of Caption
+    procedure ComputeMinCaption; virtual;
+    procedure SetCaption(const AValue: String); virtual;
+  public
+    function GetMinWidthIntrinsicContentBox: TFresnelLength; override;
+    function GetPreferredContentBox_MaxWidth(MaxWidth: TFresnelLength): TFresnelPoint;
+      override;
+    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString;
+      override;
+    procedure ClearCSSValues; override;
+    class function CSSTypeID: TCSSNumericalID; override;
+    class function CSSTypeName: TCSSString; override;
+    property Caption: String read FCaption write SetCaption;
+  end;
+
+  { TFresnelBody }
+
+  TFresnelBody = class(TFresnelElement)
+  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;
+  end;
+
+implementation
+
+{ TFresnelReplacedElement }
+
+procedure TFresnelReplacedElement.GetMinMaxPreferred(out MinWidth, MinHeight,
+  PreferredWidth, PreferredHeight, MaxWidth, MaxHeight: TFresnelLength);
+begin
+  MinWidth:=NaN;
+  MinHeight:=NaN;
+  PreferredWidth:=NaN;
+  PreferredHeight:=NaN;
+  MaxWidth:=NaN;
+  MaxHeight:=NaN;
+end;
+
+{ TFresnelSpan }
+
+class constructor TFresnelSpan.InitFresnelSpanClass;
+begin
+  // register type
+  FFresnelSpanTypeID:=RegisterCSSType(CSSTypeName);
+end;
+
+function TFresnelSpan.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 TFresnelSpan.ClearCSSValues;
+begin
+  inherited ClearCSSValues;
+  FCSSAttributes[fcaDisplayOutside]:='inline';
+  FCSSAttributes[fcaDisplayInside]:='flow';
+end;
+
+class function TFresnelSpan.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=FFresnelSpanTypeID;
+end;
+
+class function TFresnelSpan.CSSTypeName: TCSSString;
+begin
+  Result:='span';
+end;
+
+{ TFresnelDiv }
+
+class constructor TFresnelDiv.InitFresnelDivClass;
+begin
+  // register type
+  FFresnelDivTypeID:=RegisterCSSType(CSSTypeName);
+end;
+
+function TFresnelDiv.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;
+
+procedure TFresnelDiv.ClearCSSValues;
+begin
+  inherited ClearCSSValues;
+  FCSSAttributes[fcaDisplayOutside]:='block';
+  FCSSAttributes[fcaDisplayInside]:='';
+end;
+
+class function TFresnelDiv.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=FFresnelDivTypeID;
+end;
+
+class function TFresnelDiv.CSSTypeName: TCSSString;
+begin
+  Result:='div';
+end;
+
+{ TFresnelBody }
+
+procedure TFresnelBody.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);
+end;
+
+class constructor TFresnelBody.InitFresnelBodyClass;
+begin
+  FFresnelBodyTypeID:=RegisterCSSType(CSSTypeName);
+end;
+
+class function TFresnelBody.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=FFresnelBodyTypeID;
+end;
+
+class function TFresnelBody.CSSTypeName: TCSSString;
+begin
+  Result:='body';
+end;
+
+function TFresnelBody.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';
+  fcaPosition: Result:='absolute';
+  else
+    Result:=inherited GetCSSInitialAttribute(AttrID);
+  end;
+end;
+
+procedure TFresnelBody.ClearCSSValues;
+begin
+  inherited ClearCSSValues;
+  FCSSAttributes[fcaDisplayOutside]:='block';
+  FCSSAttributes[fcaPosition]:='absolute';
+end;
+
+{ TFresnelLabel }
+
+class constructor TFresnelLabel.InitFresnelLabelClass;
+begin
+  FFresnelLabelTypeID:=RegisterCSSType(CSSTypeName);
+end;
+
+procedure TFresnelLabel.ComputeMinCaption;
+// create FMinCaption from FCaption by putting every word on a line of its own
+var
+  LineBreakLen, SrcP, l, StartP, WordLen, TargetP: Integer;
+  MyLineBreak: string;
+begin
+  if flsMinCaptionValid in FLabelStates then exit;
+  Include(FLabelStates,flsMinCaptionValid);
+  if FCaption='' then
+  begin
+    FMinCaption:='';
+    exit;
+  end;
+  MyLineBreak:=sLineBreak;
+  LineBreakLen:=length(MyLineBreak);
+  SrcP:=1;
+  l:=length(FCaption);
+  SetLength(FMinCaption,l);
+  TargetP:=1;
+  while (SrcP<=l) and (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
+  if SrcP>l then
+  begin
+    // only spaces
+    FMinCaption:=' ';
+    exit;
+  end;
+  while SrcP<=l do begin
+    StartP:=SrcP;
+    while (SrcP<=l) and not (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
+    WordLen:=SrcP-StartP;
+    if TargetP+WordLen+LineBreakLen>length(FMinCaption) then
+      SetLength(FMinCaption,Max(TargetP+WordLen+LineBreakLen,length(FMinCaption)*5 div 4));
+    System.Move(FCaption[StartP],FMinCaption[TargetP],WordLen);
+    inc(TargetP,WordLen);
+    if SrcP<=l then
+    begin
+      System.Move(MyLineBreak[1],FMinCaption[TargetP],LineBreakLen);
+      inc(TargetP,LineBreakLen);
+    end;
+    while (SrcP<=l) and (FCaption[SrcP] in [' ',#9]) do inc(SrcP);
+  end;
+  SetLength(FMinCaption,TargetP-1);
+end;
+
+procedure TFresnelLabel.SetCaption(const AValue: String);
+begin
+  if FCaption=AValue then Exit;
+  FCaption:=AValue;
+  FMinCaption:='';
+  FLabelStates:=FLabelStates-[flsMinCaptionValid,flsMinWidthValid,flsSizeValid];
+end;
+
+function TFresnelLabel.GetMinWidthIntrinsicContentBox: TFresnelLength;
+var
+  p: TFresnelPoint;
+begin
+  if not (flsMinWidthValid in FLabelStates) then
+  begin
+    if not (flsMinCaptionValid in FLabelStates) then
+      ComputeMinCaption;
+    p:=Font.TextSize(FMinCaption);
+    FMinCaption:='';
+    Exclude(FLabelStates,flsMinCaptionValid);
+    FMinWidth:=p.X;
+    Include(FLabelStates,flsMinWidthValid);
+  end;
+  Result:=FMinWidth;
+end;
+
+function TFresnelLabel.GetPreferredContentBox_MaxWidth(MaxWidth: TFresnelLength
+  ): TFresnelPoint;
+begin
+  if not (flsSizeValid in FLabelStates) then
+  begin
+    FSize:=Font.TextSize(FCaption);
+    Include(FLabelStates,flsSizeValid);
+  end;
+  if MaxWidth>=FSize.X then
+    Result:=FSize
+  else
+    Result:=Font.TextSizeMaxWidth(FCaption,MaxWidth);
+end;
+
+function TFresnelLabel.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 TFresnelLabel.ClearCSSValues;
+begin
+  inherited ClearCSSValues;
+  FCSSAttributes[fcaDisplayOutside]:='inline';
+end;
+
+class function TFresnelLabel.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=FFresnelLabelTypeID;
+end;
+
+class function TFresnelLabel.CSSTypeName: TCSSString;
+begin
+  Result:='label';
+end;
+
+end.
+

+ 2742 - 0
src/fresneldom.pas

@@ -0,0 +1,2742 @@
+{
+ *****************************************************************************
+  This file is part of Fresnel.
+
+  See the file COPYING.modifiedLGPL.txt, included in this distribution,
+  for details about the license.
+ *****************************************************************************
+
+ToDo:
+- speed up GetCSSIndex
+- speed up GetCSSDepth
+- speed up GetCSSNextOfType
+- speed up GetCSSPreviousOfType
+- invalid CSS values must be skipped, so validity check must be done during css resolver
+   e.g. negative border-left-width is ignored
+-
+}
+unit FresnelDOM;
+
+{$mode ObjFPC}{$H+}
+{$WARN 6060 off} // Case statement does not handle all possible cases
+
+interface
+
+uses
+  Classes, SysUtils, Math, sortbase, LazLoggerBase, fpCSSResolver,
+  fpCSSTree, fpCSSParser, FPImage;
+
+type
+  TFresnelLength = double;
+  TFresnelPoint = record
+    X, Y: TFresnelLength;
+  end;
+  TFresnelRect = record
+    Left, Top, Right, Bottom: TFresnelLength;
+  end;
+
+  EFresnelFont = class(Exception)
+  end;
+
+const
+  MaxFresnelLength = TFresnelLength(high(longint));
+  FresnelDefaultDPI = 96;
+
+type
+  // CSS attributes of the TFresnelElement class
+  TFresnelCSSAttribute = (
+    fcaDisplay,
+    fcaDisplayBox,
+    fcaDisplayInside,
+    fcaDisplayOutside,
+    fcaPosition,
+    fcaOverflow, // x y
+    fcaOverflowX, // visible|hidden|clip|scroll|auto
+    fcaOverflowY,
+    fcaZIndex,
+    fcaClear,
+    fcaDirection,
+    fcaLeft,
+    fcaTop,
+    fcaRight,
+    fcaBottom,
+    fcaWidth,
+    fcaHeight,
+    fcaBorder, // shorthand
+    fcaBorderLeft, // shorthand
+    fcaBorderRight, // shorthand
+    fcaBorderTop, // shorthand
+    fcaBorderBottom, // shorthand
+    fcaBorderWidth, // shorthand
+    fcaBorderLeftWidth,
+    fcaBorderRightWidth,
+    fcaBorderTopWidth,
+    fcaBorderBottomWidth,
+    fcaBorderColor,
+    fcaBoxSizing,
+    fcaFloat,
+    fcaFont, // shorthand
+    fcaFontFamily,
+    fcaFontFeatureSettings,
+    fcaFontKerning, // auto|normal|none
+    fcaFontSize,  // medium|xx-small|x-small|small|large|x-large|xx-large|smaller|larger|LengthUnit|%
+    fcaFontStyle, // normal|italic|oblique
+    fcaFontWeight, // normal|bold|bolder|lighter|number
+    fcaFontVariant,
+    fcaLineHeight,
+    fcaMargin, // shorthand
+    fcaMarginLeft,
+    fcaMarginTop,
+    fcaMarginRight,
+    fcaMarginBottom,
+    fcaMarginBlock, // shorthand
+    fcaMarginBlockEnd,
+    fcaMarginBlockStart,
+    fcaMarginInline,
+    fcaMarginInlineEnd,
+    fcaMarginInlineStart,
+    fcaMinWidth,
+    fcaMaxWidth,
+    fcaMinHeight,
+    fcaMaxHeight,
+    fcaOpacity,
+    fcaPadding, // shorthand
+    fcaPaddingLeft,
+    fcaPaddingTop,
+    fcaPaddingRight,
+    fcaPaddingBottom,
+    fcaVisibility,
+    fcaBackgroundColor
+    );
+  TFresnelCSSAttributes = set of TFresnelCSSAttribute;
+
+const
+  FresnelCSSAttributeNames: array[TFresnelCSSAttribute] of string = (
+    // case sensitive!
+    'display',
+    'display-box',
+    'display-inside',
+    'display-outside',
+    'position',
+    'overflow',
+    'overflow-x',
+    'overflow-y',
+    'z-index',
+    'clear',
+    'direction',
+    'left',
+    'top',
+    'right',
+    'bottom',
+    'width',
+    'height',
+    'border',
+    'border-left',
+    'border-right',
+    'border-top',
+    'border-bottom',
+    'border-width',
+    'border-left-width',
+    'border-right-width',
+    'border-top-width',
+    'border-bottom-width',
+    'border-color',
+    'box-sizing',
+    'float',
+    'font',
+    'font-family',
+    'font-feature-settings',
+    'font-kerning',
+    'font-size',
+    'font-style',
+    'font-weight',
+    'font-variant',
+    'line-height',
+    'margin',
+    'margin-left',
+    'margin-top',
+    'margin-right',
+    'margin-bottom',
+    'margin-block',
+    'margin-block-end',
+    'margin-block-start',
+    'margin-inline',
+    'margin-inline-end',
+    'margin-inline-start',
+    'min-width',
+    'max-width',
+    'min-height',
+    'max-height',
+    'opacity',
+    'padding',
+    'padding-left',
+    'padding-top',
+    'padding-right',
+    'padding-bottom',
+    'visibility',
+    'background-color'
+    );
+
+type
+  TFresnelCSSUnit = (
+    fcuPercent,
+    fcu_cm, // centimeters
+    fcu_mm, // milimeters
+    fcu_ch, // relative to the width of the "0" (zero)
+    fcu_em, // relative to the font-size of the element
+    fcu_ex, // relative to the x-height of the current font
+    fcu_in, // inches
+    fcu_px, // pixels
+    fcu_pt, // points (1pt = 1/72 of 1in)
+    fcu_pc, // picas (1pc = 12 pt)
+    fcu_rem,// relative to font-size of the root element
+    fcu_vw, // relative to 1% of the width of the viewport
+    fcu_vh, // relative to 1% of the height of the viewport
+    fcu_vmax,// relative to 1% of viewport's larger dimension
+    fcu_vmin // relative to 1% of viewport's smaller dimension
+    );
+  TFresnelCSSUnits = set of TFresnelCSSUnit;
+
+type
+  TFresnelCSSPseudo = (
+    fcpaLang
+    );
+  TFresnelCSSPseudoAttributes = set of TFresnelCSSPseudo;
+
+const
+  FresnelCSSPseudoNames: array[TFresnelCSSPseudo] of string = (
+    ':visible'
+    );
+
+type
+  TFresnelViewport = class;
+  TFresnelElement = class;
+
+  { TFresnelLayoutNode }
+
+  TFresnelLayoutNode = class(TComponent)
+  private
+    FElement: TFresnelElement;
+    FParent: TFresnelLayoutNode;
+    FNodes: TFPList; // list of TFresnelLayoutNode
+    function GetNodeCount: integer;
+    function GetNodes(Index: integer): TFresnelLayoutNode;
+    procedure SetElement(const AValue: TFresnelElement);
+    procedure SetParent(const AValue: TFresnelLayoutNode);
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    function GetRoot: TFresnelLayoutNode;
+    procedure SortNodes(const Compare: TListSortComparer_Context; Context: Pointer); virtual;
+    property Parent: TFresnelLayoutNode read FParent write SetParent;
+    property Element: TFresnelElement read FElement write SetElement;
+    property NodeCount: integer read GetNodeCount;
+    property Nodes[Index: integer]: TFresnelLayoutNode read GetNodes;
+  end;
+  TFresnelLayoutElDataClass = class of TFresnelLayoutNode;
+
+  { TFresnelLayouter }
+
+  TFresnelLayouter = class(TComponent)
+  public
+    procedure ComputeCSS(El: TFresnelElement); virtual; abstract;
+    procedure ComputedChildrenCSS(El: TFresnelElement); virtual; abstract;
+  end;
+  TFresnelLayouterClass = class of TFresnelLayouter;
+
+  IFresnelFont = interface
+    ['{6B53C662-5598-419B-996B-7E839271B64E}']
+    function GetFamily: string;
+    function GetKerning: string;
+    function GetSize: string;
+    function GetStyle: string;
+    function GetVariant: string;
+    function GetWeight: string;
+    function TextSize(const aText: string): TFresnelPoint;
+    function TextSizeMaxWidth(const aText: string; MaxWidth: TFresnelLength): TFresnelPoint;
+    function GetTool: TObject;
+  end;
+
+  TFresnelFontDesc = record
+    Family: string;
+    Kerning: string;
+    Size: string;
+    Style: string;
+    Variant_: string;
+    Weight: string;
+  end;
+  PFresnelFontDesc = ^TFresnelFontDesc;
+
+  TFresnelLengthCheck = (
+    flcNoNegative,
+    flcNoPercentage,
+    flcInteger
+    );
+  TFresnelLengthChecks = set of TFresnelLengthCheck;
+
+  { TFresnelElement }
+
+  TFresnelElement = class(TComponent, ICSSNode)
+  private
+    function GetCSSPseudo(Pseudo: TFresnelCSSPseudo): string;
+    function GetNodeCount: integer;
+    function GetNodes(Index: integer): TFresnelElement;
+    function GetCSSElAttribute(Attr: TFresnelCSSAttribute): string;
+    function GetCSSElComputedAttribute(Attr: TFresnelCSSAttribute): string;
+    procedure SetCSSElComputedAttribute(Attr: TFresnelCSSAttribute; AValue: string);
+  private
+    FDOMIndex: integer;
+    FFont: IFresnelFont;
+    FLayoutNode: TFresnelLayoutNode;
+    FFontDesc: TFresnelFontDesc;
+    FFontDescValid: boolean;
+ class var
+    // Stuff for registering CSS numerical IDs
+    FCSSNumericalIDs: array[TCSSNumericalIDKind] of TCSSNumericalIDs;
+    FCSSIDToName: array[TCSSNumericalIDKind] of TCSSStringDynArray;
+    FCSSIDToNameCount: array[TCSSNumericalIDKind] of integer;
+    FFresnelElementTypeID: TCSSNumericalID;
+    FFresnelElementBaseAttrID: TCSSNumericalID;
+    FFresnelElementBasePseudoID: TCSSNumericalID;
+  protected
+    FCSSAttributes: array[TFresnelCSSAttribute] of string;
+    FCSSClasses: TStrings;
+    FCSSComputed: array[TFresnelCSSAttribute] of string;
+    FChildren: TFPList; // list of TFresnelElement
+    FParent: TFresnelElement;
+    FStyle: string;
+    FStyleElements: TCSSElement;
+    FCSSPosElement: TCSSElement;
+    procedure SetCSSElAttribute(Attr: TFresnelCSSAttribute; const AValue: string); virtual;
+    function CheckCSSClear(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSDisplay(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSDisplayBox(const AValue: string): boolean; virtual;
+    function CheckCSSDisplayInside(const AValue: string): boolean; virtual;
+    function CheckCSSDisplayOutside(const AValue: string): boolean; virtual;
+    function CheckCSSPosition(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSOverflow(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSOverflowX(const AValue: string): boolean; virtual;
+    function CheckCSSOverflowY(const AValue: string): boolean; virtual;
+    function CheckCSSZIndex(const AValue: string): boolean; virtual;
+    function CheckCSSDirection(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSBorder(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSBorderLeft(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSBorderTop(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSBorderRight(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSBorderBottom(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSBorderWidth(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSBorderXWidth(Attr: TFresnelCSSAttribute; const AValue: string): boolean; virtual;
+    function CheckCSSBorderColor(const AValue: string): boolean; virtual;
+    function CheckCSSBoxSizing(const AValue: string): boolean; virtual;
+    function CheckCSSFloat(const AValue: string): boolean; virtual;
+    function CheckCSSLineHeight(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSMargin(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSMarginBlock(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckOrSetCSSMarginInline(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSMinMaxWidthHeight(Attr: TFresnelCSSAttribute; const AValue: string): boolean; virtual;
+    function CheckCSSOpacity(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSFont(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSFontKerning(const AValue: string): boolean; virtual;
+    function CheckCSSFontSize(const AValue: string): boolean; virtual;
+    function CheckCSSFontStyle(const AValue: string): boolean; virtual;
+    function CheckCSSFontWeight(const AValue: string): boolean; virtual;
+    function CheckCSSFontVariant(const AValue: string): boolean; virtual;
+    function CheckOrSetCSSPadding(const AValue: string; Check: boolean): boolean; virtual;
+    function CheckCSSVisibility(const AValue: string): boolean; virtual;
+    function CheckCSSBackgroundColor(const AValue: string): boolean; virtual;
+    function GetComputedCSSValue(AttrID: TCSSNumericalID): TCSSString;
+    procedure SetComputedCSSValue(AttrID: TCSSNumericalID; const Value: TCSSString);
+    procedure SetCSSClasses(const AValue: TStrings);
+    procedure SetParent(const AValue: TFresnelElement);
+    procedure SetStyle(const AValue: string);
+    procedure SetStyleElements(const AValue: TCSSElement);
+    class constructor InitFresnelElementClass;
+    class destructor FinalFresnelElementClass;
+    class function RegisterCSSType(const aName: string): TCSSNumericalID;
+    class function RegisterCSSAttr(const aName: string): TCSSNumericalID;
+    class function RegisterCSSPseudo(const aName: string): TCSSNumericalID;
+    procedure InitCSSResolver(aResolver: TCSSResolver); virtual;
+    procedure Notification(AComponent: TComponent; Operation: TOperation);
+      override;
+    function CSSReadNextValue(const aValue: string; var p: integer): string;
+    function CheckCSSLength(Attr: TFresnelCSSAttribute; const AValue: string; const Checks: TFresnelLengthChecks = []): boolean; virtual;
+    procedure ComputeCSSAttribute(Attr: TFresnelCSSAttribute); virtual;
+    function GetDPI(IsHorizontal: boolean): TFresnelLength; virtual;
+    function GetViewport: TFresnelViewport; virtual;
+    function ElementAttrToAttrId(Attr: TFresnelCSSAttribute): TCSSNumericalID;
+    function GetFont: IFresnelFont; virtual;
+  protected
+    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
+    procedure SetParentComponent(Value: TComponent); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure Clear;
+    function GetParentComponent: TComponent; override;
+    function HasParent: Boolean; override;
+    function GetRoot: TFresnelElement;
+    function GetPath: string; virtual;
+    property Parent: TFresnelElement read FParent write SetParent;
+    property NodeCount: integer read GetNodeCount;
+    property Nodes[Index: integer]: TFresnelElement read GetNodes; default;
+    // CSS
+    class function CSSTypeID: TCSSNumericalID; virtual;
+    class function CSSTypeName: TCSSString; virtual;
+    procedure ClearCSSValues; virtual;
+    function GetCSSAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
+    function GetCSSAttributeClass: TCSSString; virtual;
+    function GetCSSChild(const anIndex: integer): ICSSNode; virtual;
+    function GetCSSChildCount: integer; virtual;
+    function GetCSSDepth: integer; virtual;
+    function GetCSSEmpty: boolean; virtual;
+    function GetCSSID: TCSSString; virtual;
+    function GetCSSIndex: integer; virtual;
+    function GetCSSNextOfType: ICSSNode; virtual;
+    function GetCSSNextSibling: ICSSNode; virtual;
+    function GetCSSParent: ICSSNode; virtual;
+    function GetCSSPreviousOfType: ICSSNode; virtual;
+    function GetCSSPreviousSibling: ICSSNode; virtual;
+    function GetCSSPseudo(const AttrID: TCSSNumericalID): TCSSString; virtual;
+    function GetCSSTypeID: TCSSNumericalID; virtual;
+    function GetCSSTypeName: TCSSString; virtual;
+    function HasCSSAttribute(const AttrID: TCSSNumericalID): boolean; virtual;
+    function HasCSSClass(const aClassName: TCSSString): boolean; virtual;
+    function HasCSSPseudo(const AttrID: TCSSNumericalID): boolean; virtual;
+    procedure SetCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement); virtual;
+    function CheckCSSValue(AttrID: TCSSNumericalID; Value: TCSSElement
+      ): boolean; virtual;
+    function GetCSSInitialAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
+    function GetCSSInheritAttribute(const AttrID: TCSSNumericalID): TCSSString; virtual;
+    procedure ComputeCSS; virtual;
+    procedure CSSWarning(const ID: int64; Msg: string); virtual;
+    procedure CSSInvalidValueWarning(const ID: int64; Attr: TFresnelCSSAttribute; const aValue: string); virtual;
+    // CSS classes and inline style
+    property StyleElements: TCSSElement read FStyleElements write SetStyleElements;
+    // CSS attributes
+    function GetComputedCSSLength(Attr: TFresnelCSSAttribute; UseInherited: boolean; UseNaNOnFail: boolean = false): TFresnelLength; virtual; // on fail returns NaN
+    function GetComputedCSString(Attr: TFresnelCSSAttribute; UseInherited: boolean): string; virtual;
+    procedure WriteComputedAttributes(Title: string);
+    property CSSAttribute[Attr: TFresnelCSSAttribute]: string read GetCSSElAttribute write SetCSSElAttribute;
+    property CSSComputedAttribute[Attr: TFresnelCSSAttribute]: string read GetCSSElComputedAttribute write SetCSSElComputedAttribute;
+    property CSSPseudo[Pseudo: TFresnelCSSPseudo]: string read GetCSSPseudo;
+    class property FresnelElementBaseAttrID: TCSSNumericalID read FFresnelElementBaseAttrID;
+    class property FresnelElementBasePseudoID: TCSSNumericalID read FFresnelElementBasePseudoID;
+    // layouter
+    function GetMaxWidthIntrinsicContentBox: TFresnelLength; virtual; // this element, excluding children, ignoring min-width
+    function GetMaxWidthContentBox: TFresnelLength; virtual; // this element, excluding children
+    function GetMaxWidthBorderBox: TFresnelLength; virtual; // this element, excluding children
+    function GetMinWidthIntrinsicContentBox: TFresnelLength; virtual; // this element, excluding children, ignoring min-width
+    function GetMinWidthContentBox: TFresnelLength; virtual; // this element, excluding children
+    function GetMinWidthBorderBox: TFresnelLength; virtual; // this element, excluding children
+    function GetPreferredContentBox_MaxWidth(MaxWidth: TFresnelLength): TFresnelPoint; virtual; // this element, excluding children
+    function GetPreferredBorderBox_MaxWidth(MaxWidth: TFresnelLength): TFresnelPoint; virtual; // this element, excluding children
+    property DOMIndex: integer read FDOMIndex write FDOMIndex;
+    property LayoutNode: TFresnelLayoutNode read FLayoutNode write FLayoutNode;
+    // font
+    property Font: IFresnelFont read GetFont write FFont;
+  published
+    property CSSClasses: TStrings read FCSSClasses write SetCSSClasses;
+    property Style: string read FStyle write SetStyle;
+  end;
+  TFresnelElementClass = class of TFresnelElement;
+  TFresnelElementArray = array of TFresnelElement;
+
+  TFresnelViewportLength = (
+    vlFontMinSize,
+    vlDPIHorizontal,
+    vlDPIVertical,
+    vlHorizontalScrollbarWidth,
+    vlVerticalScrollbarWidth
+    );
+  TFresnelViewportLengths = set of TFresnelViewportLength;
+
+  { TFresnelFontEngine }
+
+  TFresnelFontEngine = class(TComponent)
+  public
+    function Allocate(const Desc: TFresnelFontDesc): IFresnelFont; virtual; abstract;
+  end;
+
+  { TFresnelViewport }
+
+  TFresnelViewport = class(TFresnelElement,IFPObserver)
+  private
+    FCSSResolver: TCSSResolver;
+    FFontEngine: TFresnelFontEngine;
+    FLayouter: TFresnelLayouter;
+    FStylesheetElements: TCSSElement;
+    FStylesheet: TStrings;
+    FDPI: array[boolean] of TFresnelLength;
+    FFontMinSize: TFresnelLength;
+    FScrollbarWidth: array[boolean] of TFresnelLength;
+    FHeight: TFresnelLength;
+    FWidth: TFresnelLength;
+    FMaxPreferredWidth: TFresnelLength;
+    function GetHeight: TFresnelLength;
+    function GetMaxPreferredWidth: TFresnelLength;
+    function GetScrollbarWidth(IsHorizontal: boolean): TFresnelLength;
+    function GetVPLength(l: TFresnelViewportLength): TFresnelLength;
+    function GetWidth: TFresnelLength;
+    procedure SetFontMinSize(const AValue: TFresnelLength);
+    procedure SetHeight(AValue: TFresnelLength);
+    procedure SetMaxPreferredWidth(const AValue: TFresnelLength);
+    procedure SetScrollbarWidth(IsHorizontal: boolean;
+      const AValue: TFresnelLength);
+    procedure SetStylesheet(AValue: TStrings);
+    procedure SetVPLength(l: TFresnelViewportLength;
+      const AValue: TFresnelLength);
+    procedure SetWidth(AValue: TFresnelLength);
+  protected
+    function GetDPI(IsHorizontal: boolean): TFresnelLength; override;
+    procedure SetDPI(IsHorizontal: boolean; const AValue: TFresnelLength);
+    procedure SetCSSElAttribute(Attr: TFresnelCSSAttribute; const AValue: string
+      ); override;
+    procedure StylesheetChanged; virtual;
+    procedure UpdateStylesheetElements; virtual;
+    procedure FPOObservedChanged(ASender: TObject; {%H-}Operation: TFPObservedOperation; {%H-}Data: Pointer); virtual;
+    procedure InitCSSResolver(aResolver: TCSSResolver); override;
+    procedure Notification(AComponent: TComponent; Operation: TOperation);
+      override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure ApplyCSS; virtual;
+    procedure ClearCSSValues; override;
+    function AllocateFont(const Desc: TFresnelFontDesc): IFresnelFont; virtual;
+    property DPI[IsHorizontal: boolean]: TFresnelLength read GetDPI write SetDPI;
+    property FontMinSize: TFresnelLength read FFontMinSize write SetFontMinSize;
+    property ScrollbarWidth[IsHorizontal: boolean]: TFresnelLength read GetScrollbarWidth write SetScrollbarWidth;
+    property VPLength[l: TFresnelViewportLength]: TFresnelLength read GetVPLength write SetVPLength;
+    property CSSResolver: TCSSResolver read FCSSResolver;
+    property Stylesheet: TStrings read FStylesheet write SetStylesheet;
+    property StylesheetElements: TCSSElement read FStylesheetElements;
+    property Width: TFresnelLength read GetWidth write SetWidth;
+    property Height: TFresnelLength read GetHeight write SetHeight;
+    property MaxPreferredWidth: TFresnelLength read GetMaxPreferredWidth write SetMaxPreferredWidth;
+    property Layouter: TFresnelLayouter read FLayouter write FLayouter;
+    property FontEngine: TFresnelFontEngine read FFontEngine write FFontEngine;
+  end;
+
+const
+  FresnelCSSFormatSettings: 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;
+  );
+
+function FloatToCSSStr(const f: TFresnelLength): string;
+function CSSStrToFloat(const s: string; out l: TFresnelLength): boolean;
+
+implementation
+
+function FloatToCSSStr(const f: TFresnelLength): string;
+begin
+  Result:=FloatToStr(f,FresnelCSSFormatSettings);
+end;
+
+function CSSStrToFloat(const s: string; out l: TFresnelLength): boolean;
+var
+  Code: Integer;
+begin
+  Code:=0;
+  l:=0;
+  val(s,l,Code);
+  if Code<>0 then exit(false);
+  if IsNan(l) or (l>MaxFresnelLength) or (l<-MaxFresnelLength) then
+    Result:=false
+  else
+    Result:=true;
+end;
+
+{ TFresnelLayoutNode }
+
+procedure TFresnelLayoutNode.SetElement(const AValue: TFresnelElement);
+begin
+  if FElement=AValue then Exit;
+  if FElement<>nil then
+    FElement.FLayoutNode:=nil;
+  FElement:=AValue;
+  if FElement<>nil then
+    FElement.FLayoutNode:=Self;
+end;
+
+function TFresnelLayoutNode.GetNodeCount: integer;
+begin
+  if FNodes<>nil then
+    Result:=FNodes.Count
+  else
+    Result:=0;
+end;
+
+function TFresnelLayoutNode.GetNodes(Index: integer): TFresnelLayoutNode;
+begin
+  Result:=TFresnelLayoutNode(FNodes[Index]);
+end;
+
+procedure TFresnelLayoutNode.SetParent(const AValue: TFresnelLayoutNode);
+begin
+  if FParent=AValue then Exit;
+  if FParent<>nil then
+  begin
+    if FParent.FNodes<>nil then
+      FParent.FNodes.Remove(Self);
+  end;
+  FParent:=AValue;
+  if FParent<>nil then
+  begin
+    if FParent.FNodes=nil then
+      FParent.FNodes:=TFPList.Create;
+    FParent.FNodes.Add(Self);
+  end;
+end;
+
+constructor TFresnelLayoutNode.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+end;
+
+destructor TFresnelLayoutNode.Destroy;
+var
+  i: Integer;
+begin
+  for i:=NodeCount-1 downto 0 do
+    Nodes[i].FParent:=nil;
+  FreeAndNil(FNodes);
+  Parent:=nil;
+  inherited Destroy;
+end;
+
+function TFresnelLayoutNode.GetRoot: TFresnelLayoutNode;
+begin
+  Result:=Self;
+  while Result.Parent<>nil do
+    Result:=Result.Parent;
+end;
+
+procedure TFresnelLayoutNode.SortNodes(
+  const Compare: TListSortComparer_Context; Context: Pointer);
+begin
+  FNodes.Sort(Compare,Context,SortBase.DefaultSortingAlgorithm);
+end;
+
+{ TFresnelViewport }
+
+function TFresnelViewport.GetDPI(IsHorizontal: boolean): TFresnelLength;
+begin
+  Result:=FDPI[IsHorizontal];
+end;
+
+function TFresnelViewport.GetScrollbarWidth(IsHorizontal: boolean
+  ): TFresnelLength;
+begin
+  Result:=FScrollbarWidth[IsHorizontal];
+end;
+
+function TFresnelViewport.GetHeight: TFresnelLength;
+begin
+  Result:=FHeight;
+end;
+
+function TFresnelViewport.GetMaxPreferredWidth: TFresnelLength;
+begin
+  Result:=FMaxPreferredWidth;
+end;
+
+function TFresnelViewport.GetVPLength(l: TFresnelViewportLength
+  ): TFresnelLength;
+begin
+  Result:=0;
+  case l of
+    vlFontMinSize: Result:=FFontMinSize;
+    vlDPIHorizontal: Result:=FDPI[true];
+    vlDPIVertical: Result:=FDPI[false];
+    vlHorizontalScrollbarWidth: Result:=FScrollbarWidth[true];
+    vlVerticalScrollbarWidth: Result:=FScrollbarWidth[false];
+  end;
+end;
+
+function TFresnelViewport.GetWidth: TFresnelLength;
+begin
+  Result:=FWidth;
+end;
+
+procedure TFresnelViewport.SetDPI(IsHorizontal: boolean;
+  const AValue: TFresnelLength);
+begin
+  if FDPI[IsHorizontal]=AValue then exit;
+  FDPI[IsHorizontal]:=AValue;
+end;
+
+procedure TFresnelViewport.SetCSSElAttribute(Attr: TFresnelCSSAttribute;
+  const AValue: string);
+begin
+  // css cannot alter any viewport attributes
+  if Attr=fcaDisplay then ;
+  if AValue='' then ;
+end;
+
+procedure TFresnelViewport.StylesheetChanged;
+begin
+  // ToDo: call async
+  UpdateStylesheetElements;
+end;
+
+procedure TFresnelViewport.UpdateStylesheetElements;
+var
+  ss: TStringStream;
+  aParser: TCSSParser;
+begin
+  if FStylesheetElements<>nil then exit;
+  aParser:=nil;
+  ss:=TStringStream.Create(Stylesheet.Text);
+  try
+    aParser:=TCSSParser.Create(ss);
+    FStylesheetElements:=aParser.Parse;
+  finally
+    aParser.Free;
+  end;
+end;
+
+procedure TFresnelViewport.FPOObservedChanged(ASender: TObject;
+  Operation: TFPObservedOperation; Data: Pointer);
+begin
+  if ASender=FStylesheet then
+    StylesheetChanged;
+end;
+
+procedure TFresnelViewport.InitCSSResolver(aResolver: TCSSResolver);
+begin
+  inherited InitCSSResolver(aResolver);
+  if CSSResolver.StyleCount=0 then
+    CSSResolver.AddStyle(StylesheetElements)
+  else
+    CSSResolver.Styles[0]:=StylesheetElements;
+end;
+
+procedure TFresnelViewport.Notification(AComponent: TComponent;
+  Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if Operation=opRemove then
+  begin
+    if AComponent=FLayouter then
+      FLayouter:=nil
+    else if AComponent=FCSSResolver then
+      FCSSResolver:=nil;
+  end;
+end;
+
+procedure TFresnelViewport.SetFontMinSize(const AValue: TFresnelLength);
+begin
+  if FFontMinSize=AValue then Exit;
+  FFontMinSize:=AValue;
+end;
+
+procedure TFresnelViewport.SetHeight(AValue: TFresnelLength);
+begin
+  if FHeight=AValue then exit;
+  FHeight:=AValue;
+end;
+
+procedure TFresnelViewport.SetMaxPreferredWidth(const AValue: TFresnelLength);
+begin
+  if AValue=FMaxPreferredWidth then exit;
+  FMaxPreferredWidth:=AValue;
+end;
+
+procedure TFresnelViewport.SetScrollbarWidth(IsHorizontal: boolean;
+  const AValue: TFresnelLength);
+begin
+  if FScrollbarWidth[IsHorizontal]=AValue then exit;
+  FScrollbarWidth[IsHorizontal]:=AValue;
+end;
+
+procedure TFresnelViewport.SetStylesheet(AValue: TStrings);
+begin
+  if AValue=nil then exit;
+  if FStylesheet=AValue then Exit;
+  if FStylesheet.Equals(AValue) then Exit;
+  FStylesheet.Assign(AValue);
+  FreeAndNil(FStylesheetElements);
+end;
+
+procedure TFresnelViewport.SetVPLength(l: TFresnelViewportLength;
+  const AValue: TFresnelLength);
+begin
+  case l of
+    vlFontMinSize: FontMinSize:=AValue;
+    vlDPIHorizontal: DPI[true]:=AValue;
+    vlDPIVertical: DPI[false]:=AValue;
+    vlHorizontalScrollbarWidth: ScrollbarWidth[true]:=AValue;
+    vlVerticalScrollbarWidth: ScrollbarWidth[false]:=AValue;
+  end;
+end;
+
+procedure TFresnelViewport.SetWidth(AValue: TFresnelLength);
+begin
+  if FWidth=AValue then exit;
+  FWidth:=AValue;
+end;
+
+destructor TFresnelViewport.Destroy;
+begin
+  FreeAndNil(FStylesheetElements);
+  FreeAndNil(FStylesheet);
+  FreeAndNil(FCSSResolver);
+  inherited Destroy;
+end;
+
+procedure TFresnelViewport.ApplyCSS;
+var
+  CurDomIndex: integer;
+
+  procedure Traverse(El: TFresnelElement);
+  var
+    i: Integer;
+  begin
+    //writeln('Traverse ',El.ClassName,' CSSTypeName=',El.CSSTypeName);
+    El.DOMIndex:=CurDomIndex;
+    inc(CurDomIndex);
+    if El<>Self then
+    begin
+      CSSResolver.Compute(El,El.StyleElements);
+      El.ComputeCSS;
+    end;
+    Layouter.ComputeCSS(El);
+    for i:=0 to El.NodeCount-1 do
+      Traverse(El.Nodes[i]);
+    Layouter.ComputedChildrenCSS(El);
+  end;
+
+begin
+  if NodeCount>1 then
+    CSSWarning(20221018152912,'TFresnelViewport.ApplyCSS NodeCount='+IntToStr(NodeCount));
+  ClearCSSValues;
+  UpdateStylesheetElements;
+  InitCSSResolver(CSSResolver);
+  //writeln('TFresnelViewport.ApplyCSS ',StylesheetElements.ClassName);
+  try
+    CurDomIndex:=0;
+    Traverse(Self);
+  finally
+    CSSResolver.ClearStyleCustomData;
+  end;
+end;
+
+procedure TFresnelViewport.ClearCSSValues;
+begin
+  inherited ClearCSSValues;
+  FCSSComputed[fcaDisplayBox]:='';
+  FCSSComputed[fcaDisplayOutside]:='block';
+  FCSSComputed[fcaDisplayInside]:='flow-root';
+  FCSSComputed[fcaPosition]:='absolute';
+  FCSSComputed[fcaWidth]:=FloatToCSSStr(Width);
+  FCSSComputed[fcaHeight]:=FloatToCSSStr(Height);
+  FCSSComputed[fcaFontFamily]:='arial';
+  FCSSComputed[fcaFontSize]:='12';
+end;
+
+function TFresnelViewport.AllocateFont(const Desc: TFresnelFontDesc
+  ): IFresnelFont;
+begin
+  if FFontEngine<>nil then
+    Result:=FFontEngine.Allocate(Desc)
+  else
+    raise EFresnelFont.Create('TFresnelViewport.AllocateFont no FontEngine');
+end;
+
+constructor TFresnelViewport.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FWidth:=800;
+  FMaxPreferredWidth:=100000;
+  FHeight:=600;
+  FDPI[false]:=FresnelDefaultDPI;
+  FDPI[true]:=FresnelDefaultDPI;
+  FFontMinSize:=6;
+  FScrollbarWidth[False]:=12;
+  FScrollbarWidth[true]:=12;
+
+  FCSSResolver:=TCSSResolver.Create(nil);
+  FCSSResolver.OwnsStyle:=false;
+  FStylesheet:=TStringList.Create(false);
+  FStylesheet.FPOAttachObserver(Self);
+end;
+
+{ TFresnelElement }
+
+function TFresnelElement.GetNodeCount: integer;
+begin
+  Result:=FChildren.Count;
+end;
+
+function TFresnelElement.GetCSSElComputedAttribute(Attr: TFresnelCSSAttribute
+  ): string;
+begin
+  Result:=FCSSComputed[Attr];
+end;
+
+procedure TFresnelElement.SetCSSElComputedAttribute(Attr: TFresnelCSSAttribute;
+  AValue: string);
+begin
+  FCSSComputed[Attr]:=AValue;
+end;
+
+procedure TFresnelElement.SetCSSClasses(const AValue: TStrings);
+begin
+  if FCSSClasses=AValue then Exit;
+  if AValue=nil then
+  begin
+    if FCSSClasses.Count=0 then exit;
+    FCSSClasses.Clear;
+  end else begin
+    if FCSSClasses.Equals(AValue) then exit;
+    FCSSClasses.Assign(AValue);
+    FCSSClasses.Delimiter:=' ';
+  end;
+end;
+
+function TFresnelElement.GetCSSPseudo(
+  Pseudo: TFresnelCSSPseudo): string;
+begin
+  Result:='';
+  case Pseudo of
+  fcpaLang: ;
+  end;
+end;
+
+function TFresnelElement.GetNodes(Index: integer): TFresnelElement;
+begin
+  Result:=TFresnelElement(FChildren[Index]);
+end;
+
+function TFresnelElement.GetCSSElAttribute(Attr: TFresnelCSSAttribute): string;
+begin
+  Result:=FCSSAttributes[Attr];
+end;
+
+procedure TFresnelElement.SetCSSElAttribute(Attr: TFresnelCSSAttribute;
+  const AValue: string);
+begin
+  writeln('TFresnelElement.SetCSSAttribute ',Name,' ',Attr,' ',AValue);
+  if FCSSAttributes[Attr]=AValue then exit;
+  FCSSAttributes[Attr]:=AValue;
+  case AValue of
+  'inherit','initial','unset': exit;
+  end;
+
+  // set shorthand attributes
+  case Attr of
+  fcaDisplay: CheckOrSetCSSDisplay(AValue,false);
+  fcaOverflow: CheckOrSetCSSOverflow(AValue,false);
+  fcaBorder: CheckOrSetCSSBorder(AValue,false);
+  fcaBorderLeft: CheckOrSetCSSBorderLeft(AValue,false);
+  fcaBorderTop: CheckOrSetCSSBorderTop(AValue,false);
+  fcaBorderRight: CheckOrSetCSSBorderRight(AValue,false);
+  fcaBorderBottom: CheckOrSetCSSBorderBottom(AValue,false);
+  fcaBorderWidth: CheckOrSetCSSBorderWidth(AValue,false);
+  fcaFont: CheckOrSetCSSFont(AValue,false);
+  fcaMargin: CheckOrSetCSSMargin(AValue,false);
+  fcaMarginBlock: CheckOrSetCSSMarginBlock(AValue,false);
+  fcaMarginInline: CheckOrSetCSSMarginInline(AValue,false);
+  fcaPadding: CheckOrSetCSSPadding(AValue,false);
+  end;
+end;
+
+function TFresnelElement.CheckCSSClear(const AValue: string): boolean;
+begin
+  case AValue of
+  'none',
+  'left',
+  'right',
+  'both',
+  'inline-start', // ltr left
+  'inline-end':  // ltr right
+    exit(true);
+  else
+    CSSInvalidValueWarning(20221031133557,fcaClear,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSDisplay(const AValue: string; Check: boolean
+  ): boolean;
+var
+  p: integer;
+  DispOutside, DispInside: String;
+begin
+  p:=Pos(' ',AValue);
+  if p>0 then
+  begin
+    p:=1;
+    DispOutside:=CSSReadNextValue(AValue,p);
+    DispInside:=CSSReadNextValue(AValue,p);
+    if Check then
+    begin
+      if not CheckCSSDisplayOutside(DispOutside) then
+        exit(false);
+      if not CheckCSSDisplayInside(DispInside) then
+        exit(false);
+    end else begin
+      SetCSSElAttribute(fcaDisplayOutside,DispOutside);
+      SetCSSElAttribute(fcaDisplayInside,DispInside);
+    end;
+  end else begin
+    case AValue of
+    'none',
+    'contents':
+      if not Check then
+      begin
+        SetCSSElAttribute(fcaDisplayBox,AValue);
+        SetCSSElAttribute(fcaDisplayInside,'');
+        SetCSSElAttribute(fcaDisplayOutside,'');
+      end;
+    'block',
+    'inline':
+      if not Check then
+      begin
+        SetCSSElAttribute(fcaDisplayBox,'');
+        SetCSSElAttribute(fcaDisplayOutside,AValue);
+        SetCSSElAttribute(fcaDisplayInside,'flow');
+      end;
+    'flow',
+    'flow-root',
+    'flex',
+    'grid':
+      if not Check then
+      begin
+        SetCSSElAttribute(fcaDisplayBox,'');
+        SetCSSElAttribute(fcaDisplayOutside,'');
+        SetCSSElAttribute(fcaDisplayInside,AValue);
+      end;
+    'inline-block':
+      if not Check then
+      begin
+        SetCSSElAttribute(fcaDisplayBox,'');
+        SetCSSElAttribute(fcaDisplayOutside,'inline');
+        SetCSSElAttribute(fcaDisplayInside,'flow-root');
+      end;
+    else
+      p:=Pos('-',AValue);
+      if p>0 then
+      begin
+        DispOutside:=LeftStr(AValue,p-1);
+        DispInside:=copy(AValue,p+1,length(AValue));
+        if Check then
+        begin
+          if not CheckCSSDisplayOutside(DispOutside) then
+            exit(false);
+          if not CheckCSSDisplayInside(DispInside) then
+            exit(false);
+        end else begin
+          SetCSSElAttribute(fcaDisplayBox,'');
+          SetCSSElAttribute(fcaDisplayOutside,DispOutside);
+          SetCSSElAttribute(fcaDisplayInside,DispInside);
+        end;
+      end else begin
+        CSSInvalidValueWarning(20221016214635,fcaDisplay,AValue);
+        exit(false);
+      end;
+    end;
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSDisplayBox(const AValue: string): boolean;
+begin
+  case AValue of
+  'none',
+  'contents':
+    exit(true);
+  else
+    CSSInvalidValueWarning(20221031085548,fcaDisplayBox,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSDisplayInside(const AValue: string): boolean;
+begin
+  case AValue of
+  'flow',
+  'flow-root',
+  'flex',
+  'grid':
+    exit(true);
+  else
+    CSSInvalidValueWarning(20221031085111,fcaDisplayInside,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSDisplayOutside(const AValue: string): boolean;
+begin
+  case AValue of
+  'block',
+  'inline':
+  //'run-in'
+    exit(true);
+  else
+    CSSInvalidValueWarning(20221031085119,fcaDisplayOutside,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSPosition(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'static',
+  'relative',
+  'absolute',
+  'fixed',
+  'sticky': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016214813,fcaPosition,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSOverflow(const AValue: string; Check: boolean
+  ): boolean;
+var
+  p: Integer;
+  X, Y: String;
+begin
+  // x, y
+  p:=1;
+  X:=CSSReadNextValue(AValue,p);
+  Y:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    if not CheckCSSOverflowX(X) then
+      exit(false);
+    if (Y<>'') and not CheckCSSOverflowY(Y) then
+      exit(false);
+  end else begin
+    if Y='' then Y:=X;
+    SetCSSElAttribute(fcaOverflowX,X);
+    SetCSSElAttribute(fcaOverflowY,Y);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSOverflowX(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'visible',
+  'hidden',
+  'clip',
+  'scroll',
+  'auto': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016214951,fcaOverflowX,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSOverflowY(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'visible',
+  'hidden',
+  'clip',
+  'scroll',
+  'auto': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016215008,fcaOverflowY,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSZIndex(const AValue: string): boolean;
+var
+  V, Code: Integer;
+begin
+  if AValue='auto' then
+    exit(true);
+  V:=0;
+  Code:=0;
+  val(AValue,V,Code);
+  if (Code>0) or (V<low(ShortInt)) or (V>high(ShortInt)) then
+  begin
+    CSSInvalidValueWarning(20221016215854,fcaZIndex,AValue);
+    Result:=false;
+  end else
+    Result:=true;
+  if V=0 then ;
+end;
+
+function TFresnelElement.CheckCSSDirection(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'lrt','rtl': Result:=true;
+  else
+    CSSInvalidValueWarning(20221019094537,fcaDirection,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorder(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aWidth: String;
+begin
+  // width, style, color
+  p:=1;
+  aWidth:=CSSReadNextValue(AValue,p);
+  //aStyle:=CSSReadNextValue(AValue,p);
+  //aColor:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    Result:=CheckCSSBorderXWidth(fcaBorderLeftWidth,aWidth);
+  end else begin
+    SetCSSElAttribute(fcaBorderLeftWidth,aWidth);
+    SetCSSElAttribute(fcaBorderTopWidth,aWidth);
+    SetCSSElAttribute(fcaBorderRightWidth,aWidth);
+    SetCSSElAttribute(fcaBorderBottomWidth,aWidth);
+    Result:=true;
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorderLeft(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aWidth: String;
+begin
+  // width, style, color
+  p:=1;
+  aWidth:=CSSReadNextValue(AValue,p);
+  //aStyle:=CSSReadNextValue(AValue,p);
+  //aColor:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    Result:=CheckCSSBorderXWidth(fcaBorderLeftWidth,aWidth);
+  end else
+  begin
+    SetCSSElAttribute(fcaBorderLeftWidth,aWidth);
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorderTop(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aWidth: String;
+begin
+  // width, style, color
+  p:=1;
+  aWidth:=CSSReadNextValue(AValue,p);
+  //aStyle:=CSSReadNextValue(AValue,p);
+  //aColor:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    Result:=CheckCSSBorderXWidth(fcaBorderTopWidth,aWidth);
+  end else
+  begin
+    SetCSSElAttribute(fcaBorderTopWidth,aWidth);
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorderRight(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aWidth: String;
+begin
+  // width, style, color
+  p:=1;
+  aWidth:=CSSReadNextValue(AValue,p);
+  //aStyle:=CSSReadNextValue(AValue,p);
+  //aColor:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    Result:=CheckCSSBorderXWidth(fcaBorderRightWidth,aWidth);
+  end else
+  begin
+    SetCSSElAttribute(fcaBorderRightWidth,aWidth);
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorderBottom(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aWidth: String;
+begin
+  // width, style, color
+  p:=1;
+  aWidth:=CSSReadNextValue(AValue,p);
+  //aStyle:=CSSReadNextValue(AValue,p);
+  //aColor:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    Result:=CheckCSSBorderXWidth(fcaBorderBottomWidth,aWidth);
+  end else
+  begin
+    SetCSSElAttribute(fcaBorderBottomWidth,aWidth);
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSBorderWidth(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aTop, aRight, aBottom, aLeft: String;
+begin
+  p:=1;
+  aTop:=CSSReadNextValue(AValue,p);
+  aRight:=CSSReadNextValue(AValue,p);
+  aBottom:=CSSReadNextValue(AValue,p);
+  aLeft:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    if not CheckCSSBorderXWidth(fcaBorderTopWidth,aTop) then
+      exit(false);
+    if (aRight<>'') and not CheckCSSBorderXWidth(fcaBorderRightWidth,aRight) then
+      exit(false);
+    if (aBottom<>'') and not CheckCSSBorderXWidth(fcaBorderBottomWidth,aBottom) then
+      exit(false);
+    if (aLeft<>'') and not CheckCSSBorderXWidth(fcaBorderLeftWidth,aLeft) then
+      exit(false);
+  end else begin
+    if aRight='' then
+      aRight:=aTop;
+    if aBottom='' then
+      aBottom:=aTop;
+    if aLeft='' then
+      aLeft:=aRight;
+    SetCSSElAttribute(fcaBorderLeftWidth,aLeft);
+    SetCSSElAttribute(fcaBorderTopWidth,aTop);
+    SetCSSElAttribute(fcaBorderRightWidth,aRight);
+    SetCSSElAttribute(fcaBorderBottomWidth,aBottom);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSBorderXWidth(Attr: TFresnelCSSAttribute;
+  const AValue: string): boolean;
+begin
+  case AValue of
+  'thin',
+  'medium',
+  'thick': Result:=true;
+  else
+    Result:=CheckCSSLength(Attr,AValue,[flcNoNegative]);
+  end
+end;
+
+function TFresnelElement.CheckCSSBorderColor(const AValue: string): boolean;
+var
+  aColor: TFPColor;
+begin
+  Result:=TryHtmlToFPColor(AValue,aColor);
+end;
+
+function TFresnelElement.CheckCSSBoxSizing(const AValue: string): boolean;
+begin
+  case AValue of
+  // ToDo 'border-box',
+  'content-box': Result:=true;
+  else
+    CSSInvalidValueWarning(20221019123227,fcaBoxSizing,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSFloat(const AValue: string): boolean;
+begin
+  case AValue of
+  'none',
+  'left',
+  'right',
+  'inline-start', // ltr depending left
+  'inline-end': // ltr depending right
+    Result:=true;
+  else
+    CSSInvalidValueWarning(20221024134429,fcaFloat,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSLineHeight(const AValue: string): boolean;
+begin
+  if AValue='normal' then
+    Result:=true
+  else
+    Result:=CheckCSSLength(fcaLineHeight,AValue,[flcNoNegative]);
+end;
+
+function TFresnelElement.CheckOrSetCSSMargin(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aTop, aRight, aBottom, aLeft: String;
+begin
+  p:=1;
+  aTop:=CSSReadNextValue(AValue,p);
+  aRight:=CSSReadNextValue(AValue,p);
+  aBottom:=CSSReadNextValue(AValue,p);
+  aLeft:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    if not CheckCSSLength(fcaMarginTop,aTop) then
+      exit(false);
+    if (aRight<>'') and not CheckCSSLength(fcaMarginRight,aRight) then
+      exit(false);
+    if (aBottom<>'') and not CheckCSSLength(fcaMarginRight,aBottom) then
+      exit(false);
+    if (aLeft<>'') and not CheckCSSLength(fcaMarginLeft,aLeft) then
+      exit(false);
+  end else begin
+    if aRight='' then
+      aRight:=aTop;
+    if aBottom='' then
+      aBottom:=aTop;
+    if aLeft='' then
+      aLeft:=aRight;
+    SetCSSElAttribute(fcaMarginLeft,aLeft);
+    SetCSSElAttribute(fcaMarginTop,aTop);
+    SetCSSElAttribute(fcaMarginRight,aRight);
+    SetCSSElAttribute(fcaMarginBottom,aBottom);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckOrSetCSSMarginBlock(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aStart, aEnd: String;
+begin
+  p:=1;
+  aStart:=CSSReadNextValue(AValue,p);
+  aEnd:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+     if not CheckCSSLength(fcaMarginBlockStart,aStart) then
+       exit(false);
+    if (aEnd<>'') and not CheckCSSLength(fcaMarginBlockEnd,aEnd) then
+      exit(false);
+  end else begin
+    if aEnd='' then
+      aEnd:=aStart;
+    SetCSSElAttribute(fcaMarginBlockStart,aStart);
+    SetCSSElAttribute(fcaMarginBlockEnd,aEnd);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckOrSetCSSMarginInline(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aStart, aEnd: String;
+begin
+  p:=1;
+  aStart:=CSSReadNextValue(AValue,p);
+  aEnd:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    if not CheckCSSLength(fcaMarginInlineStart,aStart) then
+      exit(false);
+    if (aEnd<>'') and not CheckCSSLength(fcaMarginInlineEnd,aEnd) then
+      exit(false);
+  end else begin
+    if aEnd='' then aEnd:=aStart;
+    SetCSSElAttribute(fcaMarginInlineStart,aStart);
+    SetCSSElAttribute(fcaMarginInlineEnd,aEnd);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSMinMaxWidthHeight(Attr: TFresnelCSSAttribute;
+  const AValue: string): boolean;
+begin
+  case AValue of
+  'auto',
+  'max-content',
+  'min-content': Result:=true;
+  else
+    // ToDo fit-content()
+    Result:=CheckCSSLength(Attr,AValue,[flcNoNegative]);
+  end;
+end;
+
+function TFresnelElement.CheckCSSOpacity(const AValue: string
+  ): boolean;
+var
+  l: SizeInt;
+  Opacity: TFresnelLength;
+  Code: integer;
+begin
+  // float or float%
+  if AValue='' then
+    exit(true);
+  Result:=false;
+  l:=length(AValue);
+  if AValue[l]='%' then
+  begin
+    Code:=0;
+    val(LeftStr(AValue,l-1),Opacity,Code);
+    if Code>0 then
+    begin
+      CSSInvalidValueWarning(20221019112449,fcaOpacity,AValue);
+      exit;
+    end;
+    if (Opacity<0) or (Opacity>100.0) then
+    begin
+      CSSInvalidValueWarning(20221019112449,fcaOpacity,AValue);
+      exit;
+    end;
+  end else begin
+    Code:=0;
+    val(AValue,Opacity,Code);
+    if Code>0 then
+    begin
+      CSSInvalidValueWarning(20221019112600,fcaOpacity,AValue);
+      exit;
+    end;
+    if (Opacity<0) or (Opacity>1.0) then
+    begin
+      CSSInvalidValueWarning(20221019112607,fcaOpacity,AValue);
+      exit;
+    end;
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckOrSetCSSFont(const AValue: string; Check: boolean
+  ): boolean;
+var
+  p, Code, Weight: Integer;
+  s, LineHeight: String;
+  SlashP: SizeInt;
+begin
+  p:=1;
+  s:=CSSReadNextValue(AValue,p);
+  case s of
+  'normal','italic','oblique':
+    begin
+      // first value is font-style
+      if not Check then
+        SetCSSElAttribute(fcaFontStyle,s);
+      s:=CSSReadNextValue(AValue,p);
+    end;
+  end;
+  case s of
+  'normal','small-caps':
+    begin
+      // second value is font-variant
+      if not Check then
+        SetCSSElAttribute(fcaFontVariant,s);
+      s:=CSSReadNextValue(AValue,p);
+    end;
+  end;
+  case s of
+  'normal','bold','bolder','lighter':
+    begin
+      // third value is font-weight
+      if not Check then
+        SetCSSElAttribute(fcaFontWeight,s);
+      s:=CSSReadNextValue(AValue,p);
+    end;
+  '0'..'9':
+    begin
+      Weight:=0;
+      Code:=0;
+      val(s,Weight,Code);
+      if (Code>0) or (Weight<=0) then
+      begin
+        if Check then
+          CSSWarning(20221016183759,'font expected font-weight, but got "'+s+'"');
+        exit(false);
+      end;
+      if not Check then
+        SetCSSElAttribute(fcaFontWeight,s);
+      s:=CSSReadNextValue(AValue,p);
+    end;
+  end;
+
+  SlashP:=Pos('/',s);
+  if SlashP>0 then
+  begin
+    // font-size/line-height
+    LineHeight:=copy(s,SlashP+1,length(s));
+    s:=LeftStr(s,SlashP-1);
+  end else
+    LineHeight:='';
+
+  case s of
+  'medium','xx-small','x-small','small','large','x-large','xx-large','smaller','larger':
+    begin
+      // value is font-size
+      if not Check then
+        SetCSSElAttribute(fcaFontSize,s);
+    end;
+  '0'..'9':
+    begin
+      Weight:=0;
+      Code:=0;
+      val(s,Weight,Code);
+      if Code>0 then
+      begin
+        if Check then
+          CSSWarning(20221016184818,'font expected font-size, but got "'+s+'"');
+        exit(false);
+      end;
+      if not Check then
+        SetCSSElAttribute(fcaFontSize,s);
+    end;
+  else
+    if Check then
+      CSSWarning(20221016184934,'font expected font-size, but got "'+s+'"');
+    exit(false);
+  end;
+  if (LineHeight<>'') and not Check then
+    SetCSSElAttribute(fcaLineHeight,LineHeight);
+
+  // rest is font-family
+  while (p<=length(AValue)) and (aValue[p] in [' ',#9,#10,#13]) do inc(p);
+
+  if not Check then
+    SetCSSElAttribute(fcaFontFamily,copy(AValue,p,length(AValue)));
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSFontKerning(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'auto','normal','none': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016233244,fcaFontKerning,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSFontSize(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'medium',
+  'xx-small',
+  'x-small',
+  'small',
+  'large',
+  'x-large',
+  'xx-large',
+  'smaller',
+  'larger',
+  'math': Result:=true;
+  else
+    Result:=CheckCSSLength(fcaFontSize,AValue,[flcNoNegative]);
+  end;
+end;
+
+function TFresnelElement.CheckCSSFontStyle(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'normal',
+  'italic',
+  'oblique': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016234702,fcaFontStyle,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSFontWeight(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'normal',
+  'bold',
+  'lighter',
+  'bolder',
+  '100',
+  '200',
+  '300',
+  '400',
+  '500',
+  '600',
+  '700',
+  '800',
+  '900': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016235036,fcaFontWeight,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSFontVariant(const AValue: string
+  ): boolean;
+begin
+  case AValue of
+  'normal',
+  'small-caps',
+  'none': Result:=true;
+  else
+    CSSInvalidValueWarning(20221016235254,fcaFontVariant,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckOrSetCSSPadding(const AValue: string;
+  Check: boolean): boolean;
+var
+  p: Integer;
+  aTop, aRight, aBottom, aLeft: String;
+begin
+  p:=1;
+  aTop:=CSSReadNextValue(AValue,p);
+  aRight:=CSSReadNextValue(AValue,p);
+  aBottom:=CSSReadNextValue(AValue,p);
+  aLeft:=CSSReadNextValue(AValue,p);
+  if Check then
+  begin
+    if not CheckCSSLength(fcaPaddingTop,aTop,[flcNoNegative]) then
+      exit(false);
+    if (aRight<>'') and not CheckCSSLength(fcaPaddingRight,aRight,[flcNoNegative]) then
+      exit(false);
+    if (aBottom<>'') and not CheckCSSLength(fcaPaddingBottom,aBottom,[flcNoNegative]) then
+      exit(false);
+    if (aLeft<>'') and not CheckCSSLength(fcaPaddingLeft,aBottom,[flcNoNegative]) then
+      exit(false);
+  end else begin
+    if aRight='' then
+      aRight:=aTop;
+    if aBottom='' then
+      aBottom:=aTop;
+    if aLeft='' then
+      aLeft:=aRight;
+    SetCSSElAttribute(fcaPaddingLeft,aLeft);
+    SetCSSElAttribute(fcaPaddingTop,aTop);
+    SetCSSElAttribute(fcaPaddingRight,aRight);
+    SetCSSElAttribute(fcaPaddingBottom,aBottom);
+  end;
+  Result:=true;
+end;
+
+function TFresnelElement.CheckCSSVisibility(const AValue: string): boolean;
+begin
+  case AValue of
+  'visible',
+  'hidden',
+  'collapse': Result:=true;
+  else
+    CSSInvalidValueWarning(20221031131529,fcaVisibility,AValue);
+    Result:=false;
+  end;
+end;
+
+function TFresnelElement.CheckCSSBackgroundColor(const AValue: string): boolean;
+var
+  aColor: TFPColor;
+begin
+  // ToDo: rgb(r,g,b), rgba(r,g,b,a)
+  // ToDo: hsl(), hsla()
+  // ToDo: transparent
+  // ToDo: currentcolor
+  Result:=TryHtmlToFPColor(AValue,aColor);
+end;
+
+function TFresnelElement.GetComputedCSSValue(AttrID: TCSSNumericalID
+  ): TCSSString;
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FFresnelElementBaseAttrID) then
+    exit('');
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  Result:=FCSSComputed[Attr];
+end;
+
+procedure TFresnelElement.SetComputedCSSValue(AttrID: TCSSNumericalID;
+  const Value: TCSSString);
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FFresnelElementBaseAttrID) then
+    exit;
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  FCSSComputed[Attr]:=Value;
+end;
+
+procedure TFresnelElement.SetParent(const AValue: TFresnelElement);
+begin
+  if FParent=AValue then Exit;
+  if AValue=Self then
+    raise Exception.Create('cycle');
+
+  if FParent<>nil then
+  begin
+    FParent.FChildren.Remove(Self);
+  end;
+  FParent:=AValue;
+  if FParent<>nil then
+  begin
+    FParent.FChildren.Add(Self);
+    FreeNotification(FParent);
+  end;
+end;
+
+procedure TFresnelElement.SetStyle(const AValue: string);
+var
+  ss: TStringStream;
+  aParser: TCSSParser;
+begin
+  if FStyle=AValue then Exit;
+  FStyle:=AValue;
+  FreeAndNil(FStyleElements);
+  aParser:=nil;
+  ss:=TStringStream.Create(Style);
+  try
+    aParser:=TCSSParser.Create(ss);
+    FStyleElements:=aParser.ParseInline;
+  finally
+    aParser.Free;
+  end;
+end;
+
+procedure TFresnelElement.SetStyleElements(const AValue: TCSSElement);
+begin
+  if FStyleElements=AValue then Exit;
+  FreeAndNil(FStyleElements);
+  FStyleElements:=AValue;
+end;
+
+class function TFresnelElement.RegisterCSSType(const aName: string
+  ): TCSSNumericalID;
+var
+  Old: LongInt;
+  Map: TCSSNumericalIDs;
+begin
+  Map:=FCSSNumericalIDs[nikType];
+  Old:=Map[aName];
+  if Old>0 then
+    raise Exception.Create('TFresnelElement.RegisterCSSIDType type duplicate "'+aName+'"');
+  Result:=CSSLastTypeID+100+Map.Count;
+  //writeln('TFresnelElement.RegisterCSSType ',ClassName,' aName=',aName,' Result=',Result);
+  Map[aName]:=Result;
+end;
+
+class function TFresnelElement.RegisterCSSAttr(const aName: string
+  ): TCSSNumericalID;
+var
+  Old: LongInt;
+  Map: TCSSNumericalIDs;
+begin
+  Map:=FCSSNumericalIDs[nikAttribute];
+  Old:=Map[aName];
+  if Old>0 then
+    raise Exception.Create('TFresnelElement.RegisterCSSAttr type duplicate "'+aName+'"');
+  Result:=CSSLastAttributeID+100+Map.Count;
+  Map[aName]:=Result;
+end;
+
+class function TFresnelElement.RegisterCSSPseudo(const aName: string
+  ): TCSSNumericalID;
+var
+  Old: LongInt;
+  Map: TCSSNumericalIDs;
+begin
+  Map:=FCSSNumericalIDs[nikPseudoAttribute];
+  Old:=Map[aName];
+  if Old>0 then
+    raise Exception.Create('TFresnelElement.RegisterCSSPseudo type duplicate "'+aName+'"');
+  Result:=CSSLastPseudoID+100+Map.Count;
+  Map[aName]:=Result;
+end;
+
+procedure TFresnelElement.InitCSSResolver(aResolver: TCSSResolver);
+var
+  Kind: TCSSNumericalIDKind;
+begin
+  for Kind in TCSSNumericalIDKind do
+    aResolver.NumericalIDs[Kind]:=FCSSNumericalIDs[Kind];
+end;
+
+procedure TFresnelElement.Notification(AComponent: TComponent;
+  Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if AComponent=Self then exit;
+  if Operation=opRemove then
+  begin
+    if FChildren<>nil then
+      FChildren.Remove(AComponent);
+  end;
+end;
+
+function TFresnelElement.CSSReadNextValue(const aValue: string; var p: integer
+  ): string;
+var
+  l: SizeInt;
+  StartP: Integer;
+begin
+  Result:='';
+  l:=length(aValue);
+  if p>l then exit;
+  while (p<=l) and (aValue[p] in [' ',#9,#10,#13]) do inc(p);
+  StartP:=p;
+  while (p<=l) and not (aValue[p] in [' ',#9,#10,#13]) do inc(p);
+  Result:=copy(aValue,StartP,p-StartP);
+end;
+
+procedure TFresnelElement.CSSWarning(const ID: int64; Msg: string);
+begin
+  if FCSSPosElement<>nil then
+    Msg:=FCSSPosElement.SourceFileName+'('+IntToStr(FCSSPosElement.SourceRow)+','+IntToStr(FCSSPosElement.SourceCol)+') '+Msg;
+  Msg:=Msg+'['+IntToStr(ID)+'] '+Msg;
+  DebugLn(['TFresnelElement.CSSError ',Msg]);
+end;
+
+procedure TFresnelElement.CSSInvalidValueWarning(const ID: int64;
+  Attr: TFresnelCSSAttribute; const aValue: string);
+begin
+  CSSWarning(ID,'invalid '+FresnelCSSAttributeNames[Attr]+' value "'+AValue+'"');
+end;
+
+function TFresnelElement.CheckCSSLength(Attr: TFresnelCSSAttribute;
+  const AValue: string; const Checks: TFresnelLengthChecks): boolean;
+var
+  p, StartP: PChar;
+begin
+  Result:=false;
+  if AValue='auto' then
+    Result:=true
+  else if AValue='' then
+    CSSWarning(20221016220554,'missing '+FresnelCSSAttributeNames[Attr]+' value')
+  else begin
+    // <float><unit>
+    StartP:=PChar(AValue);
+    p:=StartP;
+    if p^='-' then
+    begin
+      if flcNoNegative in Checks then
+      begin
+        CSSInvalidValueWarning(20221103222222,Attr,AValue);
+        exit;
+      end;
+      inc(p);
+    end;
+    if not (p^ in ['0'..'9']) then
+    begin
+      CSSInvalidValueWarning(20221016220901,Attr,AValue);
+      exit;
+    end;
+    inc(p);
+    while p^ in ['0'..'9'] do inc(p);
+    if p^='.' then
+    begin
+      // float
+      if flcInteger in Checks then
+      begin
+        CSSInvalidValueWarning(20221103222126,Attr,AValue);
+        exit;
+      end;
+      inc(p);
+      if not (p^ in ['0'..'9']) then
+      begin
+        CSSInvalidValueWarning(20221016221010,Attr,AValue);
+        exit;
+      end;
+      inc(p);
+      while p^ in ['0'..'9'] do inc(p);
+    end;
+    if (p^ in ['e','E']) and (p[1] in ['-','0'..'9']) then
+    begin
+      // exponent
+      if flcInteger in Checks then
+      begin
+        CSSInvalidValueWarning(20221103222139,Attr,AValue);
+        exit;
+      end;
+      inc(p);
+      if p^='-' then
+        inc(p);
+      if not (p^ in ['0'..'9']) then
+      begin
+        CSSInvalidValueWarning(20221016220901,Attr,AValue);
+        exit;
+      end;
+      inc(p);
+      while p^ in ['0'..'9'] do inc(p);
+    end;
+
+    case p^ of
+    '%':
+      begin
+        if flcNoPercentage in Checks then
+        begin
+          CSSInvalidValueWarning(20221103222155,Attr,AValue);
+          exit;
+        end;
+        inc(p);
+      end;
+    'c':
+      case p[1] of
+      'm','h': inc(p,2); // cm, ch
+      end;
+    'e':
+      case p[1] of
+      'm','x': inc(p,2); // em, ex
+      end;
+    'i':
+      if p[1]='n' then
+        inc(p,2); // in
+    'm':
+      if p[1]='m' then
+        inc(p,2); // mm
+    'p':
+      case p[1] of
+      'x','t','c': inc(p,2); // px, pt, pc
+      end;
+    'r':
+      if (p[1]='e') and (p[2]='m') then inc(p,3);
+    'v':
+      case p[1] of
+      'w','h': inc(p,2); // vw, vh
+      'm':
+        case p[2] of
+        'a': if p[3]='x' then inc(p,4);
+        'i': if p[3]='n' then inc(p,4);
+        end;
+      end;
+    end;
+    // note: no unit is ok
+    if p-StartP<>length(AValue) then
+      CSSInvalidValueWarning(20221016221747,Attr,AValue)
+    else
+      Result:=true;
+  end;
+end;
+
+procedure TFresnelElement.ComputeCSSAttribute(Attr: TFresnelCSSAttribute);
+var
+  aValue: String;
+begin
+  aValue:=FCSSAttributes[Attr];
+  if aValue='initial' then
+    aValue:=GetCSSInitialAttribute(ElementAttrToAttrId(Attr));
+  if aValue='inherit' then
+    aValue:=GetCSSInheritAttribute(ElementAttrToAttrId(Attr));
+  if aValue='unset' then
+    aValue:='';
+  FCSSComputed[Attr]:=aValue;
+end;
+
+function TFresnelElement.GetDPI(IsHorizontal: boolean): TFresnelLength;
+begin
+  if Parent<>nil then
+    Result:=Parent.GetDPI(IsHorizontal)
+  else
+    Result:=FresnelDefaultDPI;
+end;
+
+function TFresnelElement.GetViewport: TFresnelViewport;
+var
+  El: TFresnelElement;
+begin
+  El:=GetRoot;
+  if El is TFresnelViewport then
+    Result:=TFresnelViewport(El)
+  else
+    Result:=nil;
+end;
+
+function TFresnelElement.GetComputedCSSLength(Attr: TFresnelCSSAttribute;
+  UseInherited: boolean; UseNaNOnFail: boolean): TFresnelLength;
+var
+  El: TFresnelElement;
+  s: String;
+begin
+  if CSSStrToFloat(FCSSComputed[Attr],Result) then
+    exit;
+  if UseInherited then
+  begin
+    El:=Parent;
+    while El<>nil do
+    begin
+      if CSSStrToFloat(El.FCSSComputed[Attr],Result) then
+        exit;
+      El:=El.Parent;
+    end;
+  end;
+  s:=GetCSSInitialAttribute(ElementAttrToAttrId(Attr));
+  if CSSStrToFloat(s,Result) then
+    exit;
+  if UseNaNOnFail then
+    Result:=NaN
+  else
+    Result:=0;
+end;
+
+function TFresnelElement.GetComputedCSString(Attr: TFresnelCSSAttribute;
+  UseInherited: boolean): string;
+var
+  El: TFresnelElement;
+begin
+  Result:=FCSSComputed[Attr];
+  if Result<>'' then exit;
+  if UseInherited then
+  begin
+    El:=Parent;
+    while El<>nil do
+    begin
+      Result:=El.CSSComputedAttribute[Attr];
+      if Result<>'' then
+        exit;
+      El:=El.Parent;
+    end;
+  end;
+  Result:=GetCSSInitialAttribute(ElementAttrToAttrId(Attr));
+end;
+
+procedure TFresnelElement.WriteComputedAttributes(Title: string);
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  writeln('TFresnelElement.WriteComputedAttributes ',Title,' ',GetPath,'================');
+  for Attr in TFresnelCSSAttribute do
+    if FCSSComputed[Attr]<>'' then
+      writeln('  ',Attr,'="',FCSSComputed[Attr],'"');
+end;
+
+function TFresnelElement.GetMaxWidthIntrinsicContentBox: TFresnelLength;
+begin
+  Result:=GetViewport.MaxPreferredWidth;
+end;
+
+function TFresnelElement.GetMaxWidthContentBox: TFresnelLength;
+var
+  aValue: String;
+begin
+  aValue:=CSSComputedAttribute[fcaMinWidth];
+  case aValue of
+  '',
+  'auto': Result:=GetMaxWidthIntrinsicContentBox;
+  'max-content': Result:=GetPreferredContentBox_MaxWidth(GetViewport.MaxPreferredWidth).X;
+  'min-content': Result:=GetMinWidthIntrinsicContentBox;
+  else
+    Result:=GetComputedCSSLength(fcaMaxWidth,false);
+  end;
+  if Result<0 then
+    Result:=0;
+end;
+
+function TFresnelElement.GetMaxWidthBorderBox: TFresnelLength;
+begin
+  // border and padding cannot be negative
+  Result:=GetComputedCSSLength(fcaBorderLeft,false)
+         +GetComputedCSSLength(fcaBorderRight,false)
+         +GetComputedCSSLength(fcaPaddingLeft,false)
+         +GetComputedCSSLength(fcaPaddingRight,false)
+         +GetMinWidthContentBox;
+  if IsNan(Result) or (Result<0) then
+    Result:=0;
+end;
+
+function TFresnelElement.GetMinWidthIntrinsicContentBox: TFresnelLength;
+begin
+  Result:=0;
+end;
+
+function TFresnelElement.GetMinWidthContentBox: TFresnelLength;
+var
+  aValue: String;
+begin
+  aValue:=CSSComputedAttribute[fcaMinWidth];
+  case aValue of
+  '',
+  'auto': Result:=GetMinWidthIntrinsicContentBox;
+  'max-content': Result:=GetPreferredContentBox_MaxWidth(GetViewport.MaxPreferredWidth).X;
+  'min-content': Result:=GetMinWidthIntrinsicContentBox;
+  else
+    Result:=GetComputedCSSLength(fcaMinWidth,false);
+  end;
+  if Result<0 then
+    Result:=0;
+end;
+
+function TFresnelElement.GetMinWidthBorderBox: TFresnelLength;
+begin
+  // border and padding cannot be negative
+  Result:=GetComputedCSSLength(fcaBorderLeft,false)
+         +GetComputedCSSLength(fcaBorderRight,false)
+         +GetComputedCSSLength(fcaPaddingLeft,false)
+         +GetComputedCSSLength(fcaPaddingRight,false)
+         +GetMinWidthContentBox;
+  if Result<0 then
+    Result:=0;
+end;
+
+function TFresnelElement.GetPreferredContentBox_MaxWidth(
+  MaxWidth: TFresnelLength): TFresnelPoint;
+begin
+  Result:=default(TFresnelPoint);
+  if MaxWidth=0 then ;
+end;
+
+function TFresnelElement.GetPreferredBorderBox_MaxWidth(MaxWidth: TFresnelLength
+  ): TFresnelPoint;
+var
+  ExtraWidth, ExtraHeight: TFresnelLength;
+begin
+  // border and padding cannot be negative
+  ExtraWidth:=GetComputedCSSLength(fcaBorderLeft,false)
+             +GetComputedCSSLength(fcaBorderRight,false)
+             +GetComputedCSSLength(fcaPaddingLeft,false)
+             +GetComputedCSSLength(fcaPaddingRight,false);
+  ExtraHeight:=GetComputedCSSLength(fcaBorderTop,false)
+             +GetComputedCSSLength(fcaBorderBottom,false)
+             +GetComputedCSSLength(fcaPaddingTop,false)
+             +GetComputedCSSLength(fcaPaddingBottom,false);
+  Result:=GetPreferredContentBox_MaxWidth(Max(0,MaxWidth-ExtraWidth));
+  Result.X:=Result.X+ExtraWidth;
+  Result.Y:=Result.Y+ExtraHeight;
+end;
+
+function TFresnelElement.ElementAttrToAttrId(Attr: TFresnelCSSAttribute
+  ): TCSSNumericalID;
+begin
+  Result:=ord(Attr)+FFresnelElementBaseAttrID;
+end;
+
+function TFresnelElement.GetFont: IFresnelFont;
+var
+  ViewPort: TFresnelViewport;
+begin
+  if FFontDescValid then
+    exit(FFont);
+  FFontDescValid:=true;
+  FFontDesc.Family:=GetComputedCSString(fcaFontFamily,true);
+  FFontDesc.Style:=GetComputedCSString(fcaFontStyle,true);
+  FFontDesc.Variant_:=GetComputedCSString(fcaFontVariant,true);
+  FFontDesc.Weight:=GetComputedCSString(fcaFontWeight,true);
+  FFontDesc.Size:=GetComputedCSString(fcaFontSize,true);
+  FFontDesc.Kerning:=GetComputedCSString(fcaFontKerning,true);
+  if FFont<>nil then
+  begin
+    if (FFont.GetFamily=FFontDesc.Family)
+        and (FFont.GetStyle=FFontDesc.Style)
+        and (FFont.GetVariant=FFontDesc.Variant_)
+        and (FFont.GetWeight=FFontDesc.Weight)
+        and (FFont.GetSize=FFontDesc.Size)
+        and (FFont.GetKerning=FFontDesc.Kerning) then
+      exit(FFont); // still valid
+    FFont:=nil;
+  end;
+
+  ViewPort:=GetViewport;
+  FFont:=ViewPort.AllocateFont(FFontDesc);
+  Result:=FFont;
+end;
+
+function TFresnelElement.HasParent: Boolean;
+begin
+  Result:=Parent<>nil;
+end;
+
+procedure TFresnelElement.SetParentComponent(Value: TComponent);
+begin
+  if Value is TFresnelElement then
+    Parent:=TFresnelElement(Value);
+end;
+
+procedure TFresnelElement.GetChildren(Proc: TGetChildProc; Root: TComponent);
+var
+  i: Integer;
+begin
+  for i:=0 to NodeCount-1 do
+    if Nodes[i].Owner=Root then
+      Proc(Nodes[i]);
+
+  if Root = Self then
+    for i:=0 to ComponentCount-1 do
+      if Components[i].GetParentComponent = nil then
+        Proc(Components[i]);
+end;
+
+function TFresnelElement.GetParentComponent: TComponent;
+begin
+  Result:=Parent;
+end;
+
+constructor TFresnelElement.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FChildren:=TFPList.Create;
+  FCSSClasses:=TStringList.Create;
+  FCSSClasses.Delimiter:=' ';
+end;
+
+destructor TFresnelElement.Destroy;
+begin
+  Clear;
+  FreeAndNil(FLayoutNode);
+  FreeAndNil(FChildren);
+  FreeAndNil(FCSSClasses);
+  inherited Destroy;
+end;
+
+procedure TFresnelElement.Clear;
+var
+  i: Integer;
+begin
+  FCSSClasses.Clear;
+  for i:=NodeCount-1 downto 0 do
+    Nodes[i].Parent:=nil;
+  FChildren.Clear;
+end;
+
+function TFresnelElement.GetRoot: TFresnelElement;
+begin
+  Result:=Self;
+  while Result.Parent<>nil do
+    Result:=Result.Parent;
+end;
+
+function TFresnelElement.GetPath: string;
+begin
+  if Parent<>nil then
+    Result:=Parent.GetPath+'.'
+  else
+    Result:='';
+  if Name='' then
+    Result:=Result+ClassName
+  else
+    Result:=Result+Name;
+end;
+
+class constructor TFresnelElement.InitFresnelElementClass;
+var
+  Kind: TCSSNumericalIDKind;
+  Attr: TFresnelCSSAttribute;
+  id: TCSSNumericalID;
+  PseudoAttr: TFresnelCSSPseudo;
+begin
+  for Kind in TCSSNumericalIDKind do
+  begin
+    FCSSNumericalIDs[Kind]:=TCSSNumericalIDs.Create(Kind);
+    FCSSIDToName[Kind]:=nil;
+    FCSSIDToNameCount[Kind]:=0;
+  end;
+
+  // register type
+  FFresnelElementTypeID:=RegisterCSSType(CSSTypeName);
+
+  // register attributes
+  FFresnelElementBaseAttrID:=CSSIDNone;
+  for Attr in TFresnelCSSAttribute do
+  begin
+    id:=RegisterCSSAttr(FresnelCSSAttributeNames[Attr]);
+    if FFresnelElementBaseAttrID=CSSIDNone then
+      FFresnelElementBaseAttrID:=id;
+  end;
+
+  // register pseudo attributes
+  FFresnelElementBasePseudoID:=CSSIDNone;
+  for PseudoAttr in TFresnelCSSPseudo do
+  begin
+    id:=RegisterCSSPseudo(FresnelCSSPseudoNames[PseudoAttr]);
+    if FFresnelElementBasePseudoID=CSSIDNone then
+      FFresnelElementBasePseudoID:=id;
+  end;
+end;
+
+class destructor TFresnelElement.FinalFresnelElementClass;
+var
+  Kind: TCSSNumericalIDKind;
+begin
+  for Kind in TCSSNumericalIDKind do
+  begin
+    FreeAndNil(FCSSNumericalIDs[Kind]);
+    FCSSIDToName[Kind]:=nil;
+    FCSSIDToNameCount[Kind]:=0;
+  end;
+end;
+
+class function TFresnelElement.CSSTypeID: TCSSNumericalID;
+begin
+  Result:=FFresnelElementTypeID;
+end;
+
+class function TFresnelElement.CSSTypeName: TCSSString;
+begin
+  Result:='element';
+end;
+
+procedure TFresnelElement.ClearCSSValues;
+var
+  Attr: TFresnelCSSAttribute;
+  i: Integer;
+begin
+  for Attr in TFresnelCSSAttribute do
+  begin
+    FCSSAttributes[Attr]:='';
+    FCSSComputed[Attr]:='';
+  end;
+  for i:=0 to NodeCount-1 do
+    Nodes[i].ClearCSSValues;
+end;
+
+function TFresnelElement.GetCSSAttribute(const AttrID: TCSSNumericalID
+  ): TCSSString;
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>FFresnelElementBaseAttrID+ord(High(TFresnelCSSAttribute))) then
+    exit('');
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  Result:=CSSAttribute[Attr];
+end;
+
+function TFresnelElement.GetCSSAttributeClass: TCSSString;
+begin
+  FCSSClasses.Delimiter:=' ';
+  Result:=FCSSClasses.DelimitedText;
+end;
+
+function TFresnelElement.GetCSSChild(const anIndex: integer): ICSSNode;
+begin
+  Result:=Nodes[anIndex];
+end;
+
+function TFresnelElement.GetCSSChildCount: integer;
+begin
+  Result:=NodeCount;
+end;
+
+function TFresnelElement.GetCSSDepth: integer;
+var
+  aChild: TFresnelElement;
+begin
+  // ToDo: store
+  Result:=0;
+  aChild:=Parent;
+  while aChild<>nil do
+  begin
+    inc(Result);
+    aChild:=aChild.Parent;
+  end;
+end;
+
+function TFresnelElement.GetCSSEmpty: boolean;
+begin
+  Result:=NodeCount=0;
+end;
+
+function TFresnelElement.GetCSSID: TCSSString;
+begin
+  Result:=Name;
+end;
+
+function TFresnelElement.GetCSSIndex: integer;
+begin
+  // ToDo: store
+  if Parent=nil then
+    Result:=-1
+  else
+    Result:=Parent.FChildren.IndexOf(Self);
+end;
+
+function TFresnelElement.GetCSSNextOfType: ICSSNode;
+var
+  i, Cnt: Integer;
+  MyID: TCSSNumericalID;
+  aChild: TFresnelElement;
+begin
+  Result:=nil;
+  i:=GetCSSIndex;
+  if i<0 then exit;
+  inc(i);
+  MyID:=CSSTypeID;
+  Cnt:=Parent.NodeCount;
+  while i<Cnt do
+  begin
+    aChild:=Parent[i];
+    if aChild.CSSTypeID=MyID then
+      exit(aChild);
+    inc(i);
+  end;
+end;
+
+function TFresnelElement.GetCSSNextSibling: ICSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if (i<0) or (i+1>=Parent.NodeCount) then
+    Result:=nil
+  else
+    Result:=Parent[i+1];
+end;
+
+function TFresnelElement.GetCSSParent: ICSSNode;
+begin
+  Result:=Parent;
+end;
+
+function TFresnelElement.GetCSSPreviousOfType: ICSSNode;
+var
+  i: Integer;
+  MyID: TCSSNumericalID;
+  aChild: TFresnelElement;
+begin
+  Result:=nil;
+  i:=GetCSSIndex;
+  if i<0 then exit;
+  dec(i);
+  MyID:=CSSTypeID;
+  while i>=0 do
+  begin
+    aChild:=Parent[i];
+    if aChild.CSSTypeID=MyID then
+      exit(aChild);
+    dec(i);
+  end;
+end;
+
+function TFresnelElement.GetCSSPreviousSibling: ICSSNode;
+var
+  i: Integer;
+begin
+  i:=GetCSSIndex;
+  if i<1 then
+    Result:=nil
+  else
+    Result:=Parent[i-1];
+end;
+
+function TFresnelElement.GetCSSPseudo(const AttrID: TCSSNumericalID
+  ): TCSSString;
+var
+  Pseudo: TFresnelCSSPseudo;
+begin
+  if (AttrID<FFresnelElementBasePseudoID) or (AttrID>FFresnelElementBasePseudoID+ord(High(TFresnelCSSPseudo))) then
+    exit('');
+  Pseudo:=TFresnelCSSPseudo(AttrID-FFresnelElementBasePseudoID);
+  Result:=CSSPseudo[Pseudo];
+end;
+
+function TFresnelElement.GetCSSTypeID: TCSSNumericalID;
+begin
+  Result:=CSSTypeID;
+end;
+
+function TFresnelElement.GetCSSTypeName: TCSSString;
+begin
+  Result:=CSSTypeName;
+end;
+
+function TFresnelElement.HasCSSAttribute(const AttrID: TCSSNumericalID
+  ): boolean;
+begin
+  Result:=(AttrID>=FFresnelElementBaseAttrID) and (AttrID<=FFresnelElementBaseAttrID+ord(High(TFresnelCSSAttribute)));
+end;
+
+function TFresnelElement.HasCSSClass(const aClassName: TCSSString): boolean;
+var
+  i: Integer;
+begin
+  for i:=0 to CSSClasses.Count-1 do
+    if aClassName=CSSClasses[i] then
+      exit(true);
+  Result:=false;
+end;
+
+function TFresnelElement.HasCSSPseudo(const AttrID: TCSSNumericalID
+  ): boolean;
+begin
+  Result:=(AttrID>=FFresnelElementBasePseudoID) and (AttrID<=FFresnelElementBasePseudoID+ord(High(TFresnelCSSPseudo)));
+end;
+
+procedure TFresnelElement.SetCSSValue(AttrID: TCSSNumericalID;
+  Value: TCSSElement);
+var
+  Attr: TFresnelCSSAttribute;
+  s: TCSSString;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FFresnelElementBaseAttrID) then
+    raise Exception.Create('TFresnelElement.SetCSSValue invalid AttrID '+IntToStr(AttrID));
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  s:=Value.AsString;
+  {$IFDEF VerboseCSSResolver}
+  writeln('TFresnelElement.SetCSSValue ',FresnelAttributeNames[Attr],':="',s,'"');
+  {$ENDIF}
+  FCSSPosElement:=Value;
+  try
+    CSSAttribute[Attr]:=s;
+  finally
+    FCSSPosElement:=nil;
+  end;
+end;
+
+function TFresnelElement.CheckCSSValue(AttrID: TCSSNumericalID;
+  Value: TCSSElement): boolean;
+var
+  Attr: TFresnelCSSAttribute;
+  s: TCSSString;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>ord(High(TFresnelCSSAttribute))+FFresnelElementBaseAttrID) then
+    raise Exception.Create('TFresnelElement.SetCSSValue invalid AttrID '+IntToStr(AttrID));
+  Result:=true;
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  s:=Value.AsString;
+  FCSSPosElement:=Value;
+  try
+    case Attr of
+    fcaDisplay: Result:=CheckOrSetCSSDisplay(s,true);
+    fcaDisplayBox: Result:=CheckCSSDisplayBox(s);
+    fcaDisplayInside: Result:=CheckCSSDisplayInside(s);
+    fcaDisplayOutside: Result:=CheckCSSDisplayOutside(s);
+    fcaPosition: Result:=CheckCSSPosition(s);
+    fcaOverflow: Result:=CheckOrSetCSSOverflow(s,true);
+    fcaOverflowX: Result:=CheckCSSOverflowX(s);
+    fcaOverflowY: Result:=CheckCSSOverflowY(s);
+    fcaZIndex: Result:=CheckCSSZIndex(s);
+    fcaClear: Result:=CheckCSSClear(s);
+    fcaDirection: Result:=CheckCSSDirection(s);
+    fcaLeft,
+    fcaTop,
+    fcaRight,
+    fcaBottom,
+    fcaWidth,
+    fcaHeight: Result:=CheckCSSLength(Attr,s,[flcNoNegative]);
+    fcaBorder: Result:=CheckOrSetCSSBorder(s,true);
+    fcaBorderLeft: Result:=CheckOrSetCSSBorderLeft(s,true);
+    fcaBorderRight: Result:=CheckOrSetCSSBorderRight(s,true);
+    fcaBorderTop: Result:=CheckOrSetCSSBorderTop(s,true);
+    fcaBorderBottom: Result:=CheckOrSetCSSBorderBottom(s,true);
+    fcaBorderWidth: Result:=CheckOrSetCSSBorderWidth(s,true);
+    fcaBorderLeftWidth,
+    fcaBorderRightWidth,
+    fcaBorderTopWidth,
+    fcaBorderBottomWidth: Result:=CheckCSSBorderXWidth(Attr,s);
+    fcaBorderColor: Result:=CheckCSSBorderColor(s);
+    fcaBoxSizing: Result:=CheckCSSBoxSizing(s);
+    fcaFloat: Result:=CheckCSSFloat(s);
+    fcaFont: Result:=CheckCSSFontKerning(s);
+    fcaFontFamily: ; //Result:=CheckCSSFontFamily(s);
+    fcaFontFeatureSettings: ; //Result:=CheckCSSFontFeatureSettings(s);
+    fcaFontKerning: Result:=CheckCSSFontKerning(s);
+    fcaFontSize: Result:=CheckCSSFontSize(s);
+    fcaFontStyle: Result:=CheckCSSFontStyle(s);
+    fcaFontWeight: Result:=CheckCSSFontWeight(s);
+    fcaFontVariant: Result:=CheckCSSFontVariant(s);
+    fcaLineHeight: Result:=CheckCSSLineHeight(s);
+    fcaMargin: Result:=CheckOrSetCSSMargin(s,true);
+    fcaMarginLeft,
+    fcaMarginTop,
+    fcaMarginRight,
+    fcaMarginBottom: Result:=CheckCSSLength(Attr,s);
+    fcaMarginBlock: Result:=CheckOrSetCSSMarginBlock(s,true);
+    fcaMarginBlockEnd,
+    fcaMarginBlockStart: Result:=CheckCSSLength(Attr,s);
+    fcaMarginInline: Result:=CheckOrSetCSSMarginInline(s,true);
+    fcaMarginInlineEnd,
+    fcaMarginInlineStart: Result:=CheckCSSLength(Attr,s);
+    fcaMinWidth,
+    fcaMaxWidth,
+    fcaMinHeight,
+    fcaMaxHeight: Result:=CheckCSSLength(Attr,s,[flcNoNegative]);
+    fcaOpacity: Result:=CheckCSSOpacity(s);
+    fcaPadding: Result:=CheckOrSetCSSPadding(s,true);
+    fcaPaddingLeft,
+    fcaPaddingTop,
+    fcaPaddingRight,
+    fcaPaddingBottom: Result:=CheckCSSLength(Attr,s,[flcNoNegative]);
+    fcaVisibility: Result:=CheckCSSVisibility(s);
+    fcaBackgroundColor: Result:=CheckCSSBackgroundColor(s);
+    end;
+  finally
+    FCSSPosElement:=nil;
+  end;
+end;
+
+function TFresnelElement.GetCSSInitialAttribute(const AttrID: TCSSNumericalID
+  ): TCSSString;
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  if (AttrID<FFresnelElementBaseAttrID) or (AttrID>FFresnelElementBaseAttrID+ord(High(TFresnelCSSAttribute))) then
+    exit('');
+  Attr:=TFresnelCSSAttribute(AttrID-FFresnelElementBaseAttrID);
+  case Attr of
+  fcaBorderLeftWidth,
+  fcaBorderRightWidth,
+  fcaBorderTopWidth,
+  fcaBorderBottomWidth: Result:='0';
+  fcaMarginLeft,
+  fcaMarginTop,
+  fcaMarginRight,
+  fcaMarginBottom: Result:='0';
+  fcaPaddingLeft,
+  fcaPaddingTop,
+  fcaPaddingRight,
+  fcaPaddingBottom: Result:='0';
+  else
+    Result:='';
+  end;
+end;
+
+function TFresnelElement.GetCSSInheritAttribute(const AttrID: TCSSNumericalID
+  ): TCSSString;
+var
+  El: TFresnelElement;
+begin
+  El:=Parent;
+  while El<>nil do
+  begin
+    Result:=El.GetCSSAttribute(AttrID);
+    if Result<>'' then exit;
+    El:=El.Parent;
+  end;
+  Result:='';
+end;
+
+procedure TFresnelElement.ComputeCSS;
+const
+  NormalAttr = [low(TFresnelCSSAttribute)..high(TFresnelCSSAttribute)]
+       -[fcaFontFamily,fcaFontSize,fcaFontStyle,fcaFontWeight];
+var
+  Attr: TFresnelCSSAttribute;
+begin
+  // lengths can depend on font, so compute it first
+  FFontDescValid:=false;
+  ComputeCSSAttribute(fcaFontFamily);
+  ComputeCSSAttribute(fcaFontSize);
+  ComputeCSSAttribute(fcaFontStyle);
+  ComputeCSSAttribute(fcaFontWeight);
+  for Attr in NormalAttr do
+    ComputeCSSAttribute(Attr);
+end;
+
+end.
+

+ 1473 - 0
src/fresnellayouter.pas

@@ -0,0 +1,1473 @@
+{
+ *****************************************************************************
+  This file is part of Fresnel.
+
+  See the file COPYING.modifiedLGPL.txt, included in this distribution,
+  for details about the license.
+ *****************************************************************************
+
+ToDo:
+
+}
+unit FresnelLayouter;
+
+{$mode ObjFPC}{$H+}
+{$Interfaces CORBA}
+{$WARN 6060 off} // Case statement does not handle all possible cases
+
+interface
+
+uses
+  Classes, SysUtils, Math, fpCSSResolver, FresnelDOM, FresnelControls,
+  LazLoggerBase;
+
+type
+  EFresnelLayout = class(Exception)
+  end;
+
+  // fresnel layout node preferred size flags
+  TFLNPreferredSizeFlag = (
+    flnpsApplyMinWidth,
+    flnpsApplyMinHeight,
+    flnpsApplyMaxWidth,
+    flnpsApplyMaxHeight
+    );
+  TFLNPreferredSizeFlags = set of TFLNPreferredSizeFlag;
+
+const
+  flnpsApplyMinMax = [flnpsApplyMinWidth,flnpsApplyMinHeight,flnpsApplyMaxWidth,flnpsApplyMaxHeight];
+
+type
+  TSimpleFresnelLayoutNode = class;
+
+  { TFLNodeLayouter }
+
+  TFLNodeLayouter = class(TComponent)
+  public
+    Node: TSimpleFresnelLayoutNode;
+    procedure Apply; virtual; abstract;
+    procedure ComputedChildrenCSS; virtual;
+    function GetMinWidthBorderBox: TFresnelLength; virtual; abstract;
+    function GetPreferredBorderBox_MaxWidth(MaxWidth: TFresnelLength): TFresnelPoint; virtual; abstract;
+    function GetViewport: TFresnelViewport;
+  end;
+  TFLNodeLayouterClass = class of TFLNodeLayouter;
+
+  { TSimpleFresnelLayoutNode }
+
+  TSimpleFresnelLayoutNode = class(TFresnelLayoutNode)
+  public
+    BlockContainer: TFresnelElement;
+    Layouter: TFLNodeLayouter;
+    SkipLayout: boolean; // e.g. element or descendant display-box:none
+    SkipRendering: boolean; // e.g. element or descendant display-box:none or visibility<>visible
+    SubParent: boolean; // a node in between: has no Layouter and has children
+    ZIndex: single;
+    destructor Destroy; override;
+    function GetMinWidthBorderBox: TFresnelLength; virtual;
+    function GetPreferredBorderBox_MaxWidth(MaxWidth: TFresnelLength; const Flags: TFLNPreferredSizeFlags = []): TFresnelPoint; virtual;
+  end;
+
+  { TFLBlockFormattingContext - flow layouter }
+
+  TFLBlockFormattingContext = class(TFLNodeLayouter)
+  protected
+    type
+
+      { TFLBFCNode - Fresnel layouter block formatting context node }
+
+      TFLBFCNode = class
+      public
+        Node: TSimpleFresnelLayoutNode;
+        BorderLeft: TFresnelLength;
+        BorderRight: TFresnelLength;
+        BorderTop: TFresnelLength;
+        BorderBottom: TFresnelLength;
+        PaddingLeft: TFresnelLength;
+        PaddingRight: TFresnelLength;
+        PaddingTop: TFresnelLength;
+        PaddingBottom: TFresnelLength;
+        MarginLeft: TFresnelLength;
+        MarginRight: TFresnelLength;
+        MarginTop: TFresnelLength;
+        MarginBottom: TFresnelLength;
+        Left: TFresnelLength;
+        Right: TFresnelLength;
+        Width: TFresnelLength;
+        Height: TFresnelLength;
+      end;
+  protected
+    FBorderLeft: TFresnelLength;
+    FBorderRight: TFresnelLength;
+    FBorderTop: TFresnelLength;
+    FBorderBottom: TFresnelLength;
+    FPaddingLeft: TFresnelLength;
+    FPaddingRight: TFresnelLength;
+    FPaddingTop: TFresnelLength;
+    FPaddingBottom: TFresnelLength;
+    FContentSize: TFresnelPoint;
+    FContentSizeMaxWidth: TFresnelLength;
+    // current line
+    FFirstLineNodeIndex: integer;
+    FLastLineNodeIndex: integer;
+    FLineBorderBoxHeight: TFresnelLength;
+    FLineBorderBoxLeft: TFresnelLength;
+    FLineBorderBoxRight: TFresnelLength;
+    FLineBorderBoxTop: TFresnelLength;
+    FLineBorderBoxBottom: TFresnelLength;
+    FLineMarginLeft: TFresnelLength;
+    FLineMarginRight: TFresnelLength;
+    FLineMarginTop: TFresnelLength;
+    FLineMarginBottom: TFresnelLength;
+    FLastLineBorderBoxBottom: TFresnelLength;
+    FLastLineMarginBottom: TFresnelLength;
+    FLineNodes: TFPList; // list of TFLBFCNode
+    procedure EndLine(Commit: boolean); virtual;
+    procedure StartLine; virtual;
+    procedure PlaceLineNodes; virtual;
+    procedure PlaceAbsoluteNode(ChildNode: TSimpleFresnelLayoutNode); virtual;
+    procedure ClearLineNodes;
+    function AddLineNode(ChildNode: TSimpleFresnelLayoutNode): TFLBFCNode; virtual;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure Apply; override;
+    procedure ComputedChildrenCSS; override;
+    function ComputeLayoutBorderBox(MaxWidth: TFresnelLength; Commit: boolean): TFresnelPoint; virtual;
+    function GetMinWidthBorderBox: TFresnelLength; override;
+    function GetPreferredBorderBox_MaxWidth(MaxWidth: TFresnelLength
+      ): TFresnelPoint; override;
+  end;
+
+  { TFLGridLayouter }
+
+  TFLGridLayouter = class(TFLNodeLayouter)
+  public
+    procedure Apply; override;
+  end;
+
+  { TFLFlexLayouter }
+
+  TFLFlexLayouter = class(TFLNodeLayouter)
+  public
+    procedure Apply; override;
+  end;
+
+  { TSimpleFresnelLayouter }
+
+  TSimpleFresnelLayouter = class(TFresnelLayouter)
+  private
+    FViewport: TFresnelViewport;
+    procedure SetViewport(const AValue: TFresnelViewport);
+  protected
+    procedure ErrorLayout(const ID: int64; const Msg: string); virtual;
+    procedure Layout(Node: TSimpleFresnelLayoutNode); virtual;
+    function CreateLayoutNode(El: TFresnelElement): TSimpleFresnelLayoutNode; virtual;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure Apply(aViewport: TFresnelViewport);
+    function NeedBlockFormattingContext(El: TFresnelElement): boolean; virtual;
+    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; virtual;
+    procedure ConvertCSSBorderWidthToPix(El: TFresnelElement; Attr: TFresnelCSSAttribute); virtual;
+    procedure ConvertCSSValueToPixel(El: TFresnelElement; Attr: TFresnelCSSAttribute; IsHorizontal: boolean); virtual;
+    procedure ComputeCSS(El: TFresnelElement); override; // called after basic CSS properties were computed, compute all lengths to px
+    procedure ComputedChildrenCSS(El: TFresnelElement); override; // called after child nodes CSS properties were computed
+    procedure SortStackingContext(LNode: TSimpleFresnelLayoutNode); virtual; // apply z-index
+    procedure WriteLayoutTree;
+    property Viewport: TFresnelViewport read FViewport write SetViewport;
+    // needs:
+    // viewport width, height, DPI, font size default, font size min
+    // scrollbar width, height
+    // current scroll x,y in ViewPort
+    // measure text width
+
+    // produces:
+    // layout tree
+    // boxes
+    // 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, {%H-}Context: Pointer): integer;
+
+implementation
+
+function CompareLayoutNodesZIndexDomIndex(Item1, Item2, Context: Pointer): integer;
+var
+  Node1: TSimpleFresnelLayoutNode absolute Item1;
+  Node2: TSimpleFresnelLayoutNode absolute Item2;
+  DomIndex1, DomIndex2: Integer;
+begin
+  if Node1.ZIndex>Node2.ZIndex then
+    Result:=1
+  else if Node1.ZIndex<Node2.ZIndex then
+    Result:=-1
+  else begin
+    DomIndex1:=Node1.Element.DOMIndex;
+    DomIndex2:=Node2.Element.DOMIndex;
+    if DomIndex1>DomIndex2 then
+      Result:=1
+    else if DomIndex1<DomIndex2 then
+      Result:=-1
+    else
+      Result:=0;
+  end;
+end;
+
+{ TFLNodeLayouter }
+
+procedure TFLNodeLayouter.ComputedChildrenCSS;
+begin
+
+end;
+
+function TFLNodeLayouter.GetViewport: TFresnelViewport;
+var
+  aRoot: TFresnelLayoutNode;
+begin
+  aRoot:=Node.GetRoot;
+  if aRoot.Element is TFresnelViewport then
+    Result:=TFresnelViewport(aRoot.Element)
+  else
+    Result:=nil;
+end;
+
+
+{ TFLFlexLayouter }
+
+procedure TFLFlexLayouter.Apply;
+begin
+
+end;
+
+{ TFLGridLayouter }
+
+procedure TFLGridLayouter.Apply;
+begin
+
+end;
+
+{ TFLBlockFormattingContext }
+
+procedure TFLBlockFormattingContext.EndLine(Commit: boolean);
+begin
+  if FFirstLineNodeIndex<0 then
+  begin
+    // line empty
+    exit;
+  end;
+  FLineBorderBoxTop:=FLastLineBorderBoxBottom
+            +Max(FLastLineMarginBottom,FLineMarginTop); // margin collapsing
+  FLineBorderBoxBottom:=FLineBorderBoxTop+FLineBorderBoxHeight;
+
+  FContentSize.X:=Max(FContentSize.X,FLineBorderBoxRight+FLineMarginRight+FPaddingRight+FBorderRight);
+  FContentSize.Y:=FLineBorderBoxBottom+FLineMarginBottom+FPaddingBottom+FBorderBottom;
+
+  if Commit then
+    PlaceLineNodes;
+  ClearLineNodes;
+
+  FLastLineBorderBoxBottom:=FLineBorderBoxBottom;
+  FLastLineMarginBottom:=FLineMarginBottom;
+end;
+
+procedure TFLBlockFormattingContext.StartLine;
+begin
+  FFirstLineNodeIndex:=-1;
+  FLastLineNodeIndex:=-1;
+  FLineBorderBoxHeight:=0;
+  FLineBorderBoxLeft:=FBorderLeft+FPaddingLeft;
+  FLineBorderBoxRight:=FLineBorderBoxLeft;
+  FLineMarginLeft:=0;
+  FLineMarginTop:=0;
+  FLineMarginRight:=0;
+  FLineMarginBottom:=0;
+end;
+
+procedure TFLBlockFormattingContext.PlaceLineNodes;
+var
+  BFCNode: TFLBFCNode;
+  ChildNode: TSimpleFresnelLayoutNode;
+  ChildEl: TFresnelElement;
+  i: Integer;
+  ChildTop, ChildBottom: TFresnelLength;
+begin
+  for i:=0 to FLineNodes.Count-1 do
+  begin
+    BFCNode:=TFLBFCNode(FLineNodes[i]);
+    ChildNode:=BFCNode.Node;
+    ChildEl:=ChildNode.Element;
+
+    ChildTop:=FLineBorderBoxTop-BFCNode.MarginTop;
+    ChildBottom:=FLineBorderBoxTop
+                +BFCNode.BorderTop+BFCNode.PaddingTop
+                +BFCNode.Height
+                +BFCNode.PaddingBottom+BFCNode.BorderBottom+BFCNode.MarginBottom;
+
+    ChildEl.CSSComputedAttribute[fcaLeft]:=FloatToCSSStr(BFCNode.Left);
+    ChildEl.CSSComputedAttribute[fcaRight]:=FloatToCSSStr(BFCNode.Right);
+    ChildEl.CSSComputedAttribute[fcaWidth]:=FloatToCSSStr(BFCNode.Width);
+    ChildEl.CSSComputedAttribute[fcaTop]:=FloatToCSSStr(ChildTop);
+    ChildEl.CSSComputedAttribute[fcaBottom]:=FloatToCSSStr(ChildBottom);
+    ChildEl.CSSComputedAttribute[fcaHeight]:=FloatToCSSStr(BFCNode.Height);
+    debugln(['TFLBlockFormattingContext.PlaceLineNodes ',ChildEl.GetPath,
+      ' Left=',ChildEl.CSSComputedAttribute[fcaLeft],
+      ' Top=',ChildEl.CSSComputedAttribute[fcaTop],
+      ' Width=',ChildEl.CSSComputedAttribute[fcaWidth],
+      ' Height=',ChildEl.CSSComputedAttribute[fcaHeight],
+      ' Right=',ChildEl.CSSComputedAttribute[fcaRight],
+      ' Bottom=',ChildEl.CSSComputedAttribute[fcaBottom],
+      '']);
+  end;
+end;
+
+procedure TFLBlockFormattingContext.PlaceAbsoluteNode(
+  ChildNode: TSimpleFresnelLayoutNode);
+var
+  ChildEl: TFresnelElement;
+  ChildLeft, ChildRight, ChildTop, ChildBottom, ChildWidth,
+    ChildHeight, ChildBorderLeft, ChildBorderRight,
+    ChildBorderTop, ChildBorderBottom, ChildPaddingLeft,
+    ChildPaddingRight, ChildPaddingTop, ChildPaddingBottom,
+    ChildMinWidth, ChildMaxWidth, ChildMinHeight, ChildMaxHeight,
+    ChildMarginLeft, ChildMarginRight, ChildMarginTop,
+    ChildMarginBottom: TFresnelLength;
+  ChildPrefBorderBox: TFresnelPoint;
+
+  procedure ComputeChildWidth;
+  begin
+    if IsNan(ChildWidth) then
+    begin
+      ChildPrefBorderBox:=ChildNode.GetPreferredBorderBox_MaxWidth(GetViewport.MaxPreferredWidth,flnpsApplyMinMax);
+      ChildWidth:=ChildPrefBorderBox.X;
+    end else begin
+      ChildMaxWidth:=ChildEl.GetComputedCSSLength(fcaMaxWidth,false,true);
+      if not IsNan(ChildMaxWidth) then
+        ChildWidth:=Min(ChildWidth,ChildMaxWidth);
+      ChildMinWidth:=ChildNode.GetMinWidthBorderBox;
+      ChildWidth:=Max(ChildWidth,ChildMinWidth);
+    end;
+  end;
+
+  procedure ComputeChildHeight;
+  begin
+    if IsNan(ChildHeight) then
+    begin
+      if (ChildPrefBorderBox.X<0) or (ChildPrefBorderBox.X>ChildWidth) then
+        ChildPrefBorderBox:=ChildNode.GetPreferredBorderBox_MaxWidth(ChildWidth,flnpsApplyMinMax);
+      ChildHeight:=ChildPrefBorderBox.Y;
+    end else begin
+      ChildMaxHeight:=ChildEl.GetComputedCSSLength(fcaMaxHeight,false,true);
+      if not IsNan(ChildMaxHeight) then
+        ChildHeight:=Min(ChildHeight,ChildMaxHeight);
+      ChildMinHeight:=ChildEl.GetComputedCSSLength(fcaMinHeight,false,true);
+      if not IsNan(ChildMinHeight) then
+        ChildHeight:=Min(ChildHeight,ChildMinHeight);
+    end;
+  end;
+
+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.GetComputedCSSLength(fcaBorderLeftWidth,false);
+  ChildBorderRight:=ChildEl.GetComputedCSSLength(fcaBorderRightWidth,false);
+  ChildBorderTop:=ChildEl.GetComputedCSSLength(fcaBorderTopWidth,false);
+  ChildBorderBottom:=ChildEl.GetComputedCSSLength(fcaBorderBottomWidth,false);
+
+  ChildPaddingLeft:=ChildEl.GetComputedCSSLength(fcaPaddingLeft,false);
+  ChildPaddingRight:=ChildEl.GetComputedCSSLength(fcaPaddingRight,false);
+  ChildPaddingTop:=ChildEl.GetComputedCSSLength(fcaPaddingTop,false);
+  ChildPaddingBottom:=ChildEl.GetComputedCSSLength(fcaPaddingBottom,false);
+
+  // width, height are contentbox
+  ChildWidth:=ChildEl.GetComputedCSSLength(fcaWidth,false,true);
+  ChildHeight:=ChildEl.GetComputedCSSLength(fcaHeight,false,true);
+  ChildPrefBorderBox.X:=-1;
+
+  // either ChildLeft or ChildRight must be set
+  if IsNan(ChildLeft) and IsNan(ChildRight) then
+    ChildLeft:=0;
+  if IsNan(ChildLeft) then
+  begin
+    // ChildRight is set
+    ComputeChildWidth;
+    ChildLeft:=ChildRight
+               -ChildMarginRight-ChildBorderRight-ChildPaddingRight
+               -ChildWidth
+               -ChildPaddingLeft-ChildBorderLeft-ChildMarginLeft;
+  end else begin
+    if IsNan(ChildRight) then
+      ComputeChildWidth
+    else
+      ChildWidth:=Max(0,ChildRight-ChildLeft);
+    ChildRight:=ChildLeft
+               +ChildMarginLeft+ChildBorderLeft+ChildPaddingLeft
+               +ChildWidth
+               +ChildPaddingRight+ChildBorderRight+ChildMarginRight;
+  end;
+
+  // either ChildTop or ChildBottom must be set
+  if IsNan(ChildTop) and IsNan(ChildBottom) then
+    ChildTop:=0;
+  if IsNan(ChildTop) then
+  begin
+    // ChildBottom is set
+    ComputeChildHeight;
+    ChildTop:=ChildBottom
+             -ChildMarginBottom-ChildBorderBottom-ChildPaddingBottom
+             -ChildHeight
+             -ChildPaddingTop-ChildBorderTop-ChildMarginTop;
+  end else begin
+    if IsNan(ChildBottom) then
+      ComputeChildHeight
+    else
+      ChildHeight:=Max(0,ChildBottom-ChildTop);
+    ChildBottom:=ChildTop
+                +ChildMarginTop+ChildBorderTop+ChildPaddingTop
+                +ChildHeight
+                +ChildPaddingBottom+ChildBorderBottom+ChildMarginBottom;
+  end;
+
+  ChildEl.CSSComputedAttribute[fcaLeft]:=FloatToCSSStr(ChildLeft);
+  ChildEl.CSSComputedAttribute[fcaRight]:=FloatToCSSStr(ChildRight);
+  ChildEl.CSSComputedAttribute[fcaWidth]:=FloatToCSSStr(ChildWidth);
+  ChildEl.CSSComputedAttribute[fcaTop]:=FloatToCSSStr(ChildTop);
+  ChildEl.CSSComputedAttribute[fcaBottom]:=FloatToCSSStr(ChildBottom);
+  ChildEl.CSSComputedAttribute[fcaHeight]:=FloatToCSSStr(ChildHeight);
+  debugln(['TFLBlockFormattingContext.PlaceAbsoluteNode ',ChildEl.GetPath,
+    ' Left=',ChildEl.CSSComputedAttribute[fcaLeft],
+    ' Top=',ChildEl.CSSComputedAttribute[fcaTop],
+    ' Width=',ChildEl.CSSComputedAttribute[fcaWidth],
+    ' Height=',ChildEl.CSSComputedAttribute[fcaHeight],
+    ' Right=',ChildEl.CSSComputedAttribute[fcaRight],
+    ' Bottom=',ChildEl.CSSComputedAttribute[fcaBottom],
+    '']);
+end;
+
+procedure TFLBlockFormattingContext.ClearLineNodes;
+var
+  i: Integer;
+begin
+  if FLineNodes=nil then
+    exit;
+  for i:=0 to FLineNodes.Count-1 do
+     TObject(FLineNodes[i]).Free;
+  FLineNodes.Clear;
+end;
+
+function TFLBlockFormattingContext.AddLineNode(
+  ChildNode: TSimpleFresnelLayoutNode): TFLBFCNode;
+begin
+  Result:=TFLBFCNode.Create;
+  if FLineNodes=nil then
+    FLineNodes:=TFPList.Create;
+  FLineNodes.Add(Result);
+  Result.Node:=ChildNode;
+end;
+
+constructor TFLBlockFormattingContext.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FContentSize.X:=-1;
+end;
+
+destructor TFLBlockFormattingContext.Destroy;
+begin
+  ClearLineNodes;
+  FreeAndNil(FLineNodes);
+  inherited Destroy;
+end;
+
+function TFLBlockFormattingContext.GetMinWidthBorderBox: TFresnelLength;
+var
+  i: Integer;
+  ChildNode: TSimpleFresnelLayoutNode;
+  El, ChildEl: TFresnelElement;
+  ChildMinWidth, MaxChildMinWidth, Margin: TFresnelLength;
+begin
+  El:=Node.Element;
+  Result:=El.GetMinWidthBorderBox;
+  MaxChildMinWidth:=0;
+  for i:=0 to Node.NodeCount-1 do
+  begin
+    ChildNode:=TSimpleFresnelLayoutNode(Node.Nodes[i]);
+    ChildMinWidth:=ChildNode.GetMinWidthBorderBox;
+    ChildEl:=ChildNode.Element;
+    Margin:=Max(0,ChildEl.GetComputedCSSLength(fcaMarginLeft,false));
+    ChildMinWidth:=ChildMinWidth+Margin;
+    Margin:=Max(0,ChildEl.GetComputedCSSLength(fcaMarginRight,false));
+    ChildMinWidth:=ChildMinWidth+Margin;
+    if MaxChildMinWidth<ChildMinWidth then
+      MaxChildMinWidth:=ChildMinWidth;
+  end;
+  Result:=Result+MaxChildMinWidth;
+end;
+
+function TFLBlockFormattingContext.GetPreferredBorderBox_MaxWidth(
+  MaxWidth: TFresnelLength): TFresnelPoint;
+begin
+  Result:=ComputeLayoutBorderBox(MaxWidth,false);
+end;
+
+procedure TFLBlockFormattingContext.Apply;
+var
+  MaxWidth: TFresnelLength;
+begin
+  Node.Element.WriteComputedAttributes('TFLBlockFormattingContext.Apply');
+
+  MaxWidth:=Node.Element.GetComputedCSSLength(fcaWidth,false);
+  ComputeLayoutBorderBox(MaxWidth,true);
+end;
+
+procedure TFLBlockFormattingContext.ComputedChildrenCSS;
+var
+  El: TFresnelElement;
+begin
+  inherited ComputedChildrenCSS;
+  El:=Node.Element;
+
+  FBorderLeft:=El.GetComputedCSSLength(fcaBorderLeft,false);
+  FBorderTop:=El.GetComputedCSSLength(fcaBorderTop,false);
+  FBorderRight:=El.GetComputedCSSLength(fcaBorderRight,false);
+  FBorderBottom:=El.GetComputedCSSLength(fcaBorderBottom,false);
+  FPaddingLeft:=El.GetComputedCSSLength(fcaPaddingLeft,false);
+  FPaddingTop:=El.GetComputedCSSLength(fcaPaddingTop,false);
+  FPaddingRight:=El.GetComputedCSSLength(fcaPaddingRight,false);
+  FPaddingBottom:=El.GetComputedCSSLength(fcaPaddingBottom,false);
+  FContentSize.X:=-1;
+  FContentSize.Y:=-1;
+end;
+
+function TFLBlockFormattingContext.ComputeLayoutBorderBox(
+  MaxWidth: TFresnelLength; Commit: boolean): TFresnelPoint;
+var
+  NodeIndex: Integer;
+  ChildNode: TSimpleFresnelLayoutNode;
+  ChildEl: TFresnelElement;
+  aPosition, aDisplayOutside: String;
+  IsInline: Boolean;
+  NewChildRight, MaxChildRight, CurSpace, OldMarginBoxBottom,
+    ChildMarginLeft, ChildMarginRight, ChildMarginTop, ChildMarginBottom,
+    ChildMinWidth, ChildMaxWidth,
+    ChildWidth, ChildHeight, ChildMinHeight, ChildMaxHeight,
+    ChildBorderLeft, ChildBorderRight, ChildBorderTop, ChildBorderBottom,
+    ChildPaddingLeft, ChildPaddingRight, ChildPaddingTop, ChildPaddingBottom,
+    ChildLeft, ChildRight, ChildBorderBoxHeight: TFresnelLength;
+  ChildPrefBorderBox: TFresnelPoint;
+
+  procedure AddLineNodeCache;
+  var
+    N: TFLBFCNode;
+  begin
+    debugln(['AddLineNodeCache ',ChildEl.GetPath,' L=',ChildLeft,',R=',ChildRight,',W=',ChildWidth,',H=',ChildHeight]);
+    N:=AddLineNode(ChildNode);
+    N.BorderLeft:=ChildBorderLeft;
+    N.BorderRight:=ChildBorderRight;
+    N.BorderTop:=ChildBorderTop;
+    N.BorderBottom:=ChildBorderBottom;
+    N.PaddingLeft:=ChildPaddingLeft;
+    N.PaddingRight:=ChildPaddingRight;
+    N.PaddingTop:=ChildPaddingTop;
+    N.PaddingBottom:=ChildPaddingBottom;
+    N.MarginLeft:=ChildMarginLeft;
+    N.MarginRight:=ChildMarginRight;
+    N.MarginTop:=ChildMarginTop;
+    N.MarginBottom:=ChildMarginBottom;
+    N.Left:=ChildLeft;
+    N.Right:=ChildRight;
+    N.Width:=ChildWidth;
+    N.Height:=ChildHeight;
+  end;
+
+begin
+  if (not Commit) and (FContentSize.X>=0) and (FContentSizeMaxWidth=MaxWidth) then
+    exit(FContentSize); // already computed, return last result
+
+  Result:=default(TFresnelPoint);
+  FContentSize:=default(TFresnelPoint);
+  FContentSizeMaxWidth:=MaxWidth;
+  // ToDo: css float and clear
+  // ToDo: css line-height
+  // ToDo: fcaMarginBlockEnd, fcaMarginBlockStart, fcaMarginInlineEnd, fcaMarginInlineEnd
+
+  MaxChildRight:=Max(0,MaxWidth-FBorderRight-FPaddingRight);
+  FLastLineBorderBoxBottom:=FBorderTop+FPaddingTop;
+  FLastLineMarginBottom:=0;
+
+  // AddLineNodeCache elements to the line, till full
+  StartLine;
+  NodeIndex:=0;
+  while NodeIndex<Node.NodeCount do
+  begin
+    ChildNode:=TSimpleFresnelLayoutNode(Node.Nodes[NodeIndex]);
+    ChildEl:=ChildNode.Element;
+    //debugln(['TFLBlockFormattingContext.ComputeLayoutBorderBox ',ChildEl.Name]);
+
+    // skip position: absolute and fixed
+    aPosition:=ChildEl.CSSComputedAttribute[fcaPosition];
+    case aPosition of
+    '','static','relative','sticky': ;
+    else
+      if Commit then
+        PlaceAbsoluteNode(ChildNode);
+      inc(NodeIndex);
+      continue;
+    end;
+
+    // display-outside inline or bock
+    aDisplayOutside:=ChildEl.CSSComputedAttribute[fcaDisplayOutside];
+    case aDisplayOutside of
+    '',
+    'inline': IsInline:=true;
+    else
+      // block -> end line, put element on a line of its own
+      IsInline:=false;
+      EndLine(Commit);
+      StartLine;
+    end;
+
+    ChildMarginLeft:=ChildEl.GetComputedCSSLength(fcaMarginLeft,false);
+    ChildMarginRight:=ChildEl.GetComputedCSSLength(fcaMarginRight,false);
+    ChildMarginTop:=ChildEl.GetComputedCSSLength(fcaMarginTop,false);
+    ChildMarginBottom:=ChildEl.GetComputedCSSLength(fcaMarginBottom,false);
+
+    ChildBorderLeft:=ChildEl.GetComputedCSSLength(fcaBorderLeftWidth,false);
+    ChildBorderRight:=ChildEl.GetComputedCSSLength(fcaBorderRightWidth,false);
+    ChildBorderTop:=ChildEl.GetComputedCSSLength(fcaBorderTopWidth,false);
+    ChildBorderBottom:=ChildEl.GetComputedCSSLength(fcaBorderBottomWidth,false);
+
+    ChildPaddingLeft:=ChildEl.GetComputedCSSLength(fcaPaddingLeft,false);
+    ChildPaddingRight:=ChildEl.GetComputedCSSLength(fcaPaddingRight,false);
+    ChildPaddingTop:=ChildEl.GetComputedCSSLength(fcaPaddingTop,false);
+    ChildPaddingBottom:=ChildEl.GetComputedCSSLength(fcaPaddingBottom,false);
+
+    CurSpace:=Max(0,MaxChildRight-FLineBorderBoxRight-Max(FLineMarginRight,ChildMarginLeft)-ChildMarginRight);
+
+    // 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);
+
+    if (not IsNan(ChildMinWidth)) and (not IsNan(ChildMaxWidth)) and (ChildMinWidth>=ChildMaxWidth) then
+      ChildWidth:=ChildMinWidth;
+    if Commit and (not IsInline) and IsNan(ChildWidth) then
+    begin
+      // a block element expands the full line
+      ChildWidth:=CurSpace-ChildBorderLeft-ChildPaddingLeft-ChildPaddingRight-ChildBorderRight;
+    end;
+    if not IsNan(ChildWidth) then
+    begin
+      if not IsNan(ChildMaxWidth) then
+        ChildWidth:=Min(ChildWidth,ChildMaxWidth);
+      if not IsNan(ChildMinWidth) then
+        ChildWidth:=Max(ChildWidth,ChildMinWidth);
+    end;
+
+    if (not IsNan(ChildMinHeight)) and (not IsNan(ChildMaxHeight)) and (ChildMinHeight>=ChildMaxHeight) then
+      ChildHeight:=ChildMinHeight
+    else if not IsNan(ChildHeight) then
+    begin
+      if not IsNan(ChildMaxHeight) then
+        ChildHeight:=Min(ChildHeight,ChildMaxHeight);
+      if not IsNan(ChildMinHeight) then
+        ChildHeight:=Max(ChildHeight,ChildMinHeight);
+    end;
+
+    if not IsNan(ChildWidth) and not IsNan(ChildHeight) then
+    begin
+      // fixed size
+    end else begin
+      // one or both sides are dynamic
+      if IsNan(ChildWidth) then
+      begin
+        if not IsNan(ChildMaxWidth) then
+          CurSpace:=Min(CurSpace,ChildMaxWidth);
+        if not IsNan(ChildMinWidth) then
+          CurSpace:=Max(CurSpace,ChildMinWidth);
+      end else begin
+        CurSpace:=ChildWidth;
+      end;
+      ChildPrefBorderBox:=ChildNode.GetPreferredBorderBox_MaxWidth(CurSpace,flnpsApplyMinMax);
+      // apply fixed, min, max
+      if not IsNan(ChildWidth) then
+        ChildPrefBorderBox.X:=ChildWidth;
+      if not IsNan(ChildMaxWidth) then
+        ChildPrefBorderBox.X:=Min(ChildPrefBorderBox.X,ChildMaxWidth);
+      if not IsNan(ChildMinWidth) then
+        ChildPrefBorderBox.X:=Max(ChildPrefBorderBox.X,ChildMinWidth);
+      if not IsNan(ChildHeight) then
+        ChildPrefBorderBox.Y:=ChildHeight;
+      if not IsNan(ChildMaxHeight) then
+        ChildPrefBorderBox.Y:=Min(ChildPrefBorderBox.Y,ChildMaxHeight);
+      if not IsNan(ChildMinHeight) then
+        ChildPrefBorderBox.Y:=Max(ChildPrefBorderBox.Y,ChildMinHeight);
+      ChildWidth:=ChildPrefBorderBox.X;
+      ChildHeight:=ChildPrefBorderBox.Y;
+    end;
+
+    NewChildRight:=0;
+    if (FFirstLineNodeIndex>=0) and IsInline then
+    begin
+      // check if inline element fits in line
+      NewChildRight:=FLineBorderBoxRight
+                     +Max(FLineMarginRight,ChildMarginLeft) // margin collapsing
+                     +ChildBorderLeft+ChildPaddingLeft
+                     +ChildWidth
+                     +ChildPaddingRight+ChildBorderRight+ChildMarginRight;
+      if NewChildRight>MaxChildRight then
+      begin
+        // element does not fit into the line -> next line
+        EndLine(Commit);
+        StartLine;
+      end;
+    end;
+
+    ChildBorderBoxHeight:=ChildBorderTop+ChildPaddingTop+ChildHeight+ChildPaddingBottom+ChildPaddingBottom;
+    if FFirstLineNodeIndex<0 then
+    begin
+      // first element in line
+      FFirstLineNodeIndex:=NodeIndex;
+      FLastLineNodeIndex:=NodeIndex;
+
+      FLineMarginLeft:=ChildMarginLeft;
+      FLineMarginRight:=ChildMarginRight;
+      FLineMarginTop:=ChildMarginTop;
+      FLineMarginBottom:=ChildMarginBottom;
+
+      ChildLeft:=FBorderLeft+FPaddingLeft;
+      ChildRight:=ChildLeft
+                 +ChildMarginLeft+ChildBorderLeft+ChildPaddingLeft
+                 +ChildWidth
+                 +ChildPaddingRight+ChildBorderRight+ChildMarginRight;
+
+      FLineBorderBoxLeft:=ChildLeft+ChildMarginLeft;
+      FLineBorderBoxRight:=ChildRight-ChildMarginRight;
+      FLineBorderBoxHeight:=ChildBorderBoxHeight;
+
+      AddLineNodeCache;
+
+      if (ChildRight>=MaxChildRight) or not IsInline then
+      begin
+        // first element already fills the max width
+        EndLine(Commit);
+        StartLine;
+      end;
+    end else begin
+      // append element to line
+      FLastLineNodeIndex:=NodeIndex;
+
+      ChildLeft:=FLineBorderBoxRight
+                 +Max(FLineMarginRight,ChildMarginLeft) // margin collapsing
+                 -ChildMarginLeft;
+      ChildRight:=NewChildRight;
+
+      FLineBorderBoxRight:=ChildRight-ChildMarginRight;
+      FLineMarginRight:=ChildMarginRight;
+
+      // align element at the top of the line
+      FLineMarginTop:=Max(FLineMarginTop,ChildMarginTop);
+      OldMarginBoxBottom:=FLineBorderBoxHeight+FLineMarginBottom;
+      FLineBorderBoxHeight:=Max(FLineBorderBoxHeight,ChildBorderBoxHeight);
+      if OldMarginBoxBottom<ChildBorderBoxHeight+ChildMarginBottom then
+      begin
+        FLineMarginBottom:=ChildBorderBoxHeight+ChildMarginBottom-FLineBorderBoxHeight;
+      end;
+
+      AddLineNodeCache;
+    end;
+
+    inc(NodeIndex);
+  end;
+  EndLine(Commit);
+
+  Result:=FContentSize;
+end;
+
+{ TSimpleFresnelLayoutNode }
+
+destructor TSimpleFresnelLayoutNode.Destroy;
+begin
+  FreeAndNil(Layouter);
+  BlockContainer:=nil;
+  inherited Destroy;
+end;
+
+function TSimpleFresnelLayoutNode.GetMinWidthBorderBox: TFresnelLength;
+begin
+  if Layouter<>nil then
+    Result:=Layouter.GetMinWidthBorderBox
+  else
+    Result:=Element.GetMinWidthBorderBox;
+end;
+
+function TSimpleFresnelLayoutNode.GetPreferredBorderBox_MaxWidth(
+  MaxWidth: TFresnelLength; const Flags: TFLNPreferredSizeFlags): TFresnelPoint;
+
+  function GetLimit(Attr: TFresnelCSSAttribute; out aLimit: TFresnelLength): boolean;
+  begin
+    aLimit:=Element.GetComputedCSSLength(Attr,false,true);
+    Result:=not IsNan(aLimit);
+  end;
+
+var
+  Limit: TFresnelLength;
+begin
+  if Layouter<>nil then
+    Result:=Layouter.GetPreferredBorderBox_MaxWidth(MaxWidth)
+  else
+    Result:=Element.GetPreferredBorderBox_MaxWidth(MaxWidth);
+  // apply max before min
+  if (flnpsApplyMaxWidth in Flags) and GetLimit(fcaMaxWidth,Limit) then
+    Result.X:=Min(Result.X,Limit);
+  if (flnpsApplyMinWidth in Flags) and GetLimit(fcaMinWidth,Limit) then
+    Result.X:=Max(Result.X,Limit);
+  if (flnpsApplyMaxHeight in Flags) and GetLimit(fcaMaxHeight,Limit) then
+    Result.Y:=Min(Result.Y,Limit);
+  if (flnpsApplyMinHeight in Flags) and GetLimit(fcaMinHeight,Limit) then
+    Result.Y:=Max(Result.Y,Limit);
+end;
+
+{ TSimpleFresnelLayouter }
+
+procedure TSimpleFresnelLayouter.SetViewport(const AValue: TFresnelViewport);
+var
+  OldLNode: TFresnelLayoutNode;
+begin
+  if FViewport=AValue then Exit;
+  if FViewport<>nil then
+    FViewport.Layouter:=nil;
+  FViewport:=AValue;
+  if FViewport<>nil then
+  begin
+    FViewport.Layouter:=Self;
+    if FViewport.LayoutNode<>nil then
+    begin
+      OldLNode:=FViewport.LayoutNode;
+      FViewport.LayoutNode:=nil;
+      OldLNode.Free;
+    end;
+    CreateLayoutNode(FViewport);
+  end;
+end;
+
+procedure TSimpleFresnelLayouter.ErrorLayout(const ID: int64; const Msg: string);
+var
+  s: String;
+begin
+  s:='['+IntToStr(ID)+'] '+Msg;
+  DebugLn(['Error: TFresnelLayouter.ErrorLayout ',s]);
+  raise EFresnelLayout.Create(s);
+end;
+
+procedure TSimpleFresnelLayouter.Layout(Node: TSimpleFresnelLayoutNode);
+var
+  i: Integer;
+begin
+  if Node.SkipLayout then exit;
+
+  if Node.Layouter<>nil then
+    Node.Layouter.Apply;
+
+  for i:=0 to Node.NodeCount-1 do
+    Layout(TSimpleFresnelLayoutNode(Node.Nodes[i]));
+
+  // sort for z-index
+  SortStackingContext(Node);
+end;
+
+function TSimpleFresnelLayouter.CreateLayoutNode(El: TFresnelElement
+  ): TSimpleFresnelLayoutNode;
+begin
+  if El.LayoutNode<>nil then
+    Result:=TSimpleFresnelLayoutNode(El.LayoutNode)
+  else begin
+    Result:=TSimpleFresnelLayoutNode.Create(El);
+    El.LayoutNode:=Result;
+    Result.Element:=El;
+  end;
+end;
+
+constructor TSimpleFresnelLayouter.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+end;
+
+procedure TSimpleFresnelLayouter.Apply(aViewport: TFresnelViewport);
+var
+  aDisplayInside, aPosition, aDisplayOutside, aDisplayBox: String;
+  VPNode: TSimpleFresnelLayoutNode;
+begin
+  Viewport:=aViewport;
+  if Viewport=nil then
+    exit;
+
+  // sanity checks
+  if not (Viewport.LayoutNode is TSimpleFresnelLayoutNode) then
+    ErrorLayout(20221031154042,'TFresnelLayouter.Apply expected viewport.LayoutNode is TSimpleFresnelLayoutNode');
+  VPNode:=TSimpleFresnelLayoutNode(Viewport.LayoutNode);
+  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];
+  if aDisplayOutside<>'block' then
+    ErrorLayout(20221031182815,'TFresnelLayouter.Apply expected viewport.displayoutside=block, but found "'+aDisplayOutside+'"');
+  aDisplayInside:=Viewport.CSSComputedAttribute[fcaDisplayInside];
+  if aDisplayInside<>'flow-root' then
+    ErrorLayout(20221018142350,'TFresnelLayouter.Apply expected viewport.displayinside=flow-root, but found "'+aDisplayInside+'"');
+  aPosition:=Viewport.CSSComputedAttribute[fcaPosition];
+  if aPosition<>'absolute' then
+    ErrorLayout(20221031153911,'TFresnelLayouter.Apply expected viewport.position=absolute, but found "'+aPosition+'"');
+
+  Layout(VPNode);
+end;
+
+function TSimpleFresnelLayouter.NeedBlockFormattingContext(El: TFresnelElement
+  ): boolean;
+// BFC
+// contain internal and external floats
+// surpress margin collapsing
+var
+  aPosition, aOverflow, aFloat, aDisplayBox, aDisplayInside: String;
+begin
+  if El=nil then
+    exit(false);
+  if El.Parent=nil then
+    exit(true);
+
+  aPosition:=El.CSSComputedAttribute[fcaPosition];
+  case aPosition of
+  'absolute',
+  'fixed': exit(true);
+  end;
+
+  aDisplayBox:=El.CSSComputedAttribute[fcaDisplayBox];
+  if aDisplayBox='none' then
+    exit(false);
+
+  aDisplayInside:=El.CSSComputedAttribute[fcaDisplayInside];
+  case aDisplayInside of
+  'block',
+  'flow-root': exit(true);
+  end;
+
+  aOverflow:=El.CSSComputedAttribute[fcaOverflow];
+  case aOverflow of
+  'visible',
+  'clip': exit(true);
+  end;
+
+  aFloat:=El.CSSComputedAttribute[fcaFloat];
+  case aFloat of
+  '', 'none': ;
+  else
+    exit(true);
+  end;
+
+  // contain: layout, content, paint
+  // flex items
+  // grid items
+  // multicol containers
+  // column-span
+
+  Result:=false;
+end;
+
+function TSimpleFresnelLayouter.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];
+  case aPosition of
+  'absolute':
+    begin
+      // the containing block is nearest ancestor element that has a position value other than static
+      Result:=El.Parent;
+      while (Result<>nil) do
+      begin
+        aValue:=Result.CSSComputedAttribute[fcaPosition];
+        if (aValue<>'static') and (aValue<>'') then
+          exit;
+        // Note:check for transform<>'none'
+        Result:=Result.Parent;
+      end;
+      Result:=Viewport;
+    end;
+  'fixed':
+    begin
+      // viewport
+      Result:=Viewport;
+    end;
+  else
+    // static, relative, sticky
+    // the containing block is the nearest ancestor element that is either a
+    // block container (such as an inline-block, block, or list-item element)
+    // or establishes a formatting context (such as a table container,
+    // flex container, grid container, or the block container itself)
+    Result:=El.Parent;
+    while (Result<>nil) do
+    begin
+      if TSimpleFresnelLayoutNode(Result.LayoutNode).Layouter<>nil then
+        exit;
+      Result:=Result.Parent;
+    end;
+    Result:=Viewport;
+  end;
+end;
+
+procedure TSimpleFresnelLayouter.UpdateLayouter(El: TFresnelElement;
+  LNode: TSimpleFresnelLayoutNode);
+var
+  aDisplayInside: String;
+  LayouterClass: TFLNodeLayouterClass;
+begin
+  LayouterClass:=nil;
+  if (not LNode.SkipLayout) and (El.NodeCount>0) then
+  begin
+    aDisplayInside:=El.CSSComputedAttribute[fcaDisplayInside];
+    if aDisplayInside='grid' then
+      LayouterClass:=TFLGridLayouter
+    else if aDisplayInside='flex' then
+      LayouterClass:=TFLFlexLayouter
+    else if NeedBlockFormattingContext(El) then
+      LayouterClass:=TFLBlockFormattingContext;
+  end;
+  if (LayouterClass<>nil) then
+  begin
+    if (LNode.Layouter<>nil) and (LNode.Layouter.ClassType<>LayouterClass) then
+      FreeAndNil(LNode.Layouter);
+    if LNode.Layouter=nil then
+    begin
+      LNode.Layouter:=LayouterClass.Create(nil);
+      LNode.Layouter.Node:=LNode;
+    end;
+  end else begin
+    if (LNode.Layouter<>nil) then
+      FreeAndNil(LNode.Layouter);
+  end;
+end;
+
+procedure TSimpleFresnelLayouter.UpdateLayoutParent(El: TFresnelElement;
+  LNode: TSimpleFresnelLayoutNode);
+var
+  ZIndexStr, aPosition: String;
+  ZIndexInt, {%H-}Code: Integer;
+  NewParent: TFresnelElement;
+  ParentLNode: TSimpleFresnelLayoutNode;
+begin
+  debugln(['TSimpleFresnelLayouter.UpdateLayoutParent ',El.GetPath]);
+  if LNode.SkipLayout or (El.Parent=nil) then
+    LNode.Parent:=nil
+  else begin
+    aPosition:=El.CSSComputedAttribute[fcaPosition];
+    case aPosition of
+    'absolute','fixed','relative','sticky':
+      begin
+        ZIndexStr:=El.CSSComputedAttribute[fcaZIndex];
+        if (ZIndexStr='') or (ZIndexStr='auto') then
+          LNode.ZIndex:=0.0
+        else begin
+          ZIndexInt:=0;
+          val(ZIndexStr,ZIndexInt,Code);
+          LNode.ZIndex:=ZIndexInt;
+        end;
+        LNode.ZIndex:=LNode.ZIndex+0.1;
+      end;
+    else
+      LNode.ZIndex:=0;
+    end;
+
+    NewParent:=LNode.BlockContainer;
+    while (NewParent<>nil) and (TSimpleFresnelLayoutNode(NewParent.LayoutNode).Layouter=nil) do
+      NewParent:=NewParent.Parent;
+    if NewParent=nil then
+      LNode.Parent:=nil
+    else begin
+      ParentLNode:=TSimpleFresnelLayoutNode(NewParent.LayoutNode);
+      LNode.Parent:=ParentLNode;
+      if ParentLNode.Layouter=nil then
+        ParentLNode.SubParent:=true;
+    end;
+  end;
+end;
+
+function TSimpleFresnelLayouter.GetPixPerUnit(El: TFresnelElement;
+  anUnit: TFresnelCSSUnit; IsHorizontal: boolean): TFresnelLength;
+var
+  B: TFresnelLength;
+  aContainer: TFresnelElement;
+begin
+  Result:=1;
+  case anUnit of
+    fcuPercent:
+      begin
+        aContainer:=GetBlockContainer(El);
+        if aContainer<>nil then
+        begin
+          if IsHorizontal then
+          begin
+            Result:=aContainer.GetComputedCSSLength(fcaWidth,false);
+            B:=aContainer.GetComputedCSSLength(fcaPaddingLeft,false);
+            Result:=Result+B;
+            B:=aContainer.GetComputedCSSLength(fcaPaddingRight,false);
+            Result:=Result+B;
+          end
+          else begin
+            Result:=aContainer.GetComputedCSSLength(fcaHeight,false);
+            exit(0);
+            B:=aContainer.GetComputedCSSLength(fcaPaddingTop,false);
+            Result:=Result+B;
+            B:=aContainer.GetComputedCSSLength(fcaPaddingBottom,false);
+            Result:=Result+B;
+          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 TSimpleFresnelLayouter.ConvertCSSBorderWidthToPix(
+  El: TFresnelElement; Attr: TFresnelCSSAttribute);
+var
+  aWidth: String;
+begin
+  aWidth:=El.CSSComputedAttribute[Attr];
+  case aWidth of
+  'thin': El.CSSComputedAttribute[Attr]:='0.5';
+  'medium': El.CSSComputedAttribute[Attr]:='1';
+  'thick': El.CSSComputedAttribute[Attr]:='2';
+  end;
+end;
+
+procedure TSimpleFresnelLayouter.ConvertCSSValueToPixel(El: TFresnelElement;
+  Attr: TFresnelCSSAttribute; IsHorizontal: boolean);
+var
+  aValue: String;
+  StartP, p: PChar;
+  aNumber: TFresnelLength;
+begin
+  aValue:=El.CSSComputedAttribute[Attr];
+  if (AValue='auto') or (aValue='') then
+    exit;
+  StartP:=PChar(AValue);
+  p:=StartP;
+  if p^='-' then inc(p);
+  while p^ in ['0'..'9'] do inc(p);
+  if p^='.' then
+  begin
+    // float
+    inc(p);
+    while p^ in ['0'..'9'] do inc(p);
+  end;
+  if (p^ in ['e','E']) and (p[1] in ['-','0'..'9']) then
+  begin
+    // exponent
+    inc(p);
+    if p^='-' then
+      inc(p);
+    while p^ in ['0'..'9'] do inc(p);
+  end;
+  if not CSSStrToFloat(copy(aValue,1,p-StartP),aNumber) then
+  begin
+    El.CSSInvalidValueWarning(20221017003716,Attr,aValue);
+    exit;
+  end;
+
+  // convert aNumber to pixels
+  case p^ of
+  '%': aNumber:=aNumber*GetPixPerUnit(El,fcuPercent,IsHorizontal); // relative to the parent element
+  'c':
+    case p[1] of
+    'm': // cm  centimeters
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_cm,IsHorizontal);
+    'h': // ch  relative to the width of the "0" (zero)
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_ch,IsHorizontal); // ToDo
+    end;
+  'e':
+    case p[1] of
+    'm': // em  relative to the font-size of the element
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_em,IsHorizontal);
+    'x': // ex  relative to the x-height of the current font
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_ex,IsHorizontal);
+    end;
+  'i':
+    if p[1]='n' then
+      // in  inches
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_cm,IsHorizontal);
+  'm':
+    if p[1]='m' then
+      // mm  centimeters
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_mm,IsHorizontal);
+  'p':
+    case p[1] of
+    'x': ; // px  pixels
+    't': // pt  points (1pt = 1/72 of 1in)
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_pt,IsHorizontal);
+    'c': // pc  picas (1pc = 12 pt)
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_pc,IsHorizontal);
+    end;
+  'r':
+    if (p[1]='e') and (p[2]='m') then
+      // rem  relative to font-size of the root element
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_rem,IsHorizontal);
+  'v':
+    case p[1] of
+    'w': // vw  relative to 1% of the width of the viewport
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_vw,IsHorizontal);
+    'h': // vh  relative to 1% of the height of the viewport
+      aNumber:=aNumber*GetPixPerUnit(El,fcu_vh,IsHorizontal);
+    'm':
+      case p[2] of
+      'a': if p[3]='x' then  // vmax  relative to 1% of viewport's smaller dimension
+        aNumber:=aNumber*GetPixPerUnit(El,fcu_vmax,IsHorizontal);
+      'i': if p[3]='n' then  // vmin  relative to 1% of viewport's larger dimension
+        aNumber:=aNumber*GetPixPerUnit(El,fcu_vmin,IsHorizontal);
+      end;
+    end;
+  end;
+
+  El.CSSComputedAttribute[Attr]:=FloatToCSSStr(aNumber);
+  //writeln('TFresnelElement.ConvertCSSValueToPixel ',Attr,' ',FCSSComputed[Attr]);
+end;
+
+procedure TSimpleFresnelLayouter.ComputeCSS(El: TFresnelElement);
+var
+  Attr: TFresnelCSSAttribute;
+  LNode, ParentLNode: TSimpleFresnelLayoutNode;
+  aDisplayBox, aVisibility: String;
+  {%H-}Code: integer;
+begin
+  writeln('TSimpleFresnelLayouter.ComputeCSS ',El.GetPath);
+  // every node gets a layout ndoe
+  LNode:=CreateLayoutNode(El);
+
+  // inherit flags
+  if El.Parent<>nil then
+  begin
+    ParentLNode:=TSimpleFresnelLayoutNode(El.Parent.LayoutNode);
+    if ParentLNode.SkipLayout then
+      LNode.SkipLayout:=true;
+    if ParentLNode.SkipRendering then
+      LNode.SkipRendering:=true;
+  end
+  else
+    ParentLNode:=nil;
+
+  // display-box
+  aDisplayBox:=El.CSSComputedAttribute[fcaDisplayBox];
+  if aDisplayBox='none' then
+  begin
+    LNode.SkipLayout:=true;
+    LNode.SkipRendering:=true;
+  end;
+
+  // layouter
+  UpdateLayouter(El,LNode);
+
+  // visibility
+  aVisibility:=El.CSSComputedAttribute[fcaVisibility];
+  case aVisibility of
+  'hidden','collapse':
+    LNode.SkipRendering:=true;
+  end;
+
+  // block container
+  LNode.BlockContainer:=GetBlockContainer(El);
+  writeln('TSimpleFresnelLayouter.ComputeCSS ',El.Name,' BlockContainer=',DbgSName(LNode.BlockContainer));
+
+  // LayoutParent
+  UpdateLayoutParent(El,LNode);
+
+  // convert non-pixel units to pixels
+  ConvertCSSBorderWidthToPix(El,fcaBorderLeftWidth);
+  ConvertCSSBorderWidthToPix(El,fcaBorderRightWidth);
+  ConvertCSSBorderWidthToPix(El,fcaBorderTopWidth);
+  ConvertCSSBorderWidthToPix(El,fcaBorderBottomWidth);
+
+  for Attr in [
+      fcaLeft,
+      fcaRight,
+      fcaWidth,
+      fcaBorderLeftWidth,
+      fcaBorderRightWidth,
+      fcaMarginTop, // all margins are computed from the width
+      fcaMarginBottom,
+      fcaMarginLeft,
+      fcaMarginRight,
+      fcaMarginBlockEnd,
+      fcaMarginBlockStart,
+      fcaMarginInlineEnd,
+      fcaMarginInlineStart,
+      fcaMinWidth,
+      fcaMaxWidth,
+      fcaPaddingLeft,
+      fcaPaddingRight,
+      fcaPaddingTop,  // all padding are computed from the width
+      fcaPaddingBottom] do
+    ConvertCSSValueToPixel(El,Attr,true);
+  for Attr in [
+      fcaTop,
+      fcaBottom,
+      fcaHeight,
+      fcaBorderTopWidth,
+      fcaBorderBottomWidth,
+      fcaLineHeight,
+      fcaMinHeight,
+      fcaMaxHeight] do
+    ConvertCSSValueToPixel(El,Attr,false);
+end;
+
+procedure TSimpleFresnelLayouter.ComputedChildrenCSS(El: TFresnelElement);
+var
+  LNode: TSimpleFresnelLayoutNode;
+begin
+  LNode:=TSimpleFresnelLayoutNode(El.LayoutNode);
+  if LNode.Layouter<>nil then
+    LNode.Layouter.ComputedChildrenCSS;
+end;
+
+procedure TSimpleFresnelLayouter.SortStackingContext(LNode: TSimpleFresnelLayoutNode
+  );
+
+  function IsSorted: boolean;
+  var
+    NextNode, Node: TSimpleFresnelLayoutNode;
+    i: Integer;
+  begin
+    if LNode.NodeCount<=1 then
+      exit(true);
+    i:=LNode.NodeCount-1;
+    NextNode:=TSimpleFresnelLayoutNode(LNode.Nodes[i]);
+    dec(i);
+    repeat
+      Node:=TSimpleFresnelLayoutNode(LNode.Nodes[i]);
+      if (Node.ZIndex<NextNode.ZIndex) then
+        // ok
+      else if (Node.ZIndex>NextNode.ZIndex) then
+        exit(false) // need sorting
+      else if Node.Element.DOMIndex>NextNode.Element.DOMIndex then
+        exit(false); // need sorting
+      NextNode:=Node;
+      dec(i);
+    until i<0;
+    Result:=true;
+  end;
+
+begin
+  if IsSorted then exit;
+  LNode.SortNodes(@CompareLayoutNodesZIndexDomIndex,nil);
+  {$IFDEF VerboseFresnelLayouter}
+  if not IsSorted then
+    raise Exception.Create('20221031180116 TSimpleFresnelLayouter.SortLayoutNodes');
+  {$ENDIF}
+end;
+
+procedure TSimpleFresnelLayouter.WriteLayoutTree;
+
+  procedure WriteNode(const Prefix: string; Node: TSimpleFresnelLayoutNode);
+  var
+    i: Integer;
+    El: TFresnelElement;
+  begin
+    El:=Node.Element;
+    write(Prefix,El.Name,' NodeCount=',Node.NodeCount,' display-outside=',El.CSSComputedAttribute[fcaDisplayOutside],' display-inside=',El.CSSComputedAttribute[fcaDisplayInside],' position=',El.CSSComputedAttribute[fcaPosition],' z-index=',El.CSSComputedAttribute[fcaZIndex]);
+    if Node.Layouter<>nil then
+      write(' layouter=',Node.Layouter.ClassName);
+    writeln;
+    for i:=0 to Node.NodeCount-1 do
+      WriteNode(Prefix+'  ',TSimpleFresnelLayoutNode(Node.Nodes[i]));
+  end;
+
+begin
+  if Viewport=nil then
+  begin
+    writeln('TSimpleFresnelLayouter.WriteLayoutTree Viewport=nil');
+    exit;
+  end;
+  if Viewport.LayoutNode=nil then
+  begin
+    writeln('TSimpleFresnelLayouter.WriteLayoutTree Viewport.LayoutNode=nil');
+    exit;
+  end;
+  writeln('TSimpleFresnelLayouter.WriteLayoutTree BEGIN======================');
+  WriteNode('',TSimpleFresnelLayoutNode(Viewport.LayoutNode));
+  writeln('TSimpleFresnelLayouter.WriteLayoutTree END========================');
+end;
+
+end.
+

+ 532 - 0
src/fresnellclcontrols.pas

@@ -0,0 +1,532 @@
+unit FresnelLCLControls;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Types, Math, FPImage, FresnelDOM, FresnelLayouter,
+  FresnelControls, FresnelRenderer, AvgLvlTree, Laz_AVL_Tree, LazLoggerBase,
+  Graphics, Controls, LCLIntf, Forms;
+
+type
+  TFresnelLCLFontEngine = class;
+
+  { TFresnelLCLFont }
+
+  TFresnelLCLFont = class(TInterfacedObject,IFresnelFont)
+  public
+    Engine: TFresnelLCLFontEngine;
+    Family: string;
+    Kerning: string;
+    Size: string;
+    Style: string;
+    Variant_: string;
+    Weight: string;
+    LCLFont: TFont;
+    destructor Destroy; override;
+    function GetFamily: string;
+    function GetKerning: string;
+    function GetSize: string;
+    function GetStyle: string;
+    function GetVariant: string;
+    function GetWeight: string;
+    function TextSize(const aText: string): TFresnelPoint; virtual;
+    function TextSizeMaxWidth(const aText: string; MaxWidth: TFresnelLength
+      ): TFresnelPoint; virtual;
+    function GetTool: TObject;
+  end;
+
+  { TFresnelLCLFontEngine }
+
+  TFresnelLCLFontEngine = class(TFresnelFontEngine)
+  private
+    FCanvas: TCanvas;
+    FFonts: TAvgLvlTree; // tree of TFresnelLCLFont sorted for
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    function FindFont(const Desc: TFresnelFontDesc): TFresnelLCLFont; virtual;
+    function Allocate(const Desc: TFresnelFontDesc): IFresnelFont; override;
+    function TextSize(aFont: TFresnelLCLFont; const aText: string): TPoint; virtual;
+    function TextSizeMaxWidth(aFont: TFresnelLCLFont; const aText: string; MaxWidth: integer): TPoint; virtual;
+    function NeedLCLFont(aFont: TFresnelLCLFont): TFont; virtual;
+    property Canvas: TCanvas read FCanvas write FCanvas;
+  end;
+
+  { TFresnelLCLRenderer }
+
+  TFresnelLCLRenderer = class(TFresnelRenderer)
+  private
+    FCanvas: TCanvas;
+  protected
+    procedure FillRect(const aColor: TFPColor; const aRect: TFresnelRect);
+      override;
+    procedure Line(const aColor: TFPColor; const x1, y1, x2, y2: TFresnelLength); override;
+    procedure TextOut(const aLeft, aTop: TFresnelLength;
+      const aFont: IFresnelFont; const aColor: TFPColor;
+      const aText: string); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    property Canvas: TCanvas read FCanvas write FCanvas;
+  end;
+
+  { TFresnelLCLControl }
+
+  TFresnelLCLControl = class(TCustomControl)
+  private
+    FFontEngine: TFresnelLCLFontEngine;
+    FLayouter: TSimpleFresnelLayouter;
+    FRenderer: TFresnelLCLRenderer;
+    FViewport: TFresnelViewport;
+    procedure SetLayouter(const AValue: TSimpleFresnelLayouter);
+    procedure SetViewport(const AValue: TFresnelViewport);
+  protected
+    procedure Notification(AComponent: TComponent; Operation: TOperation);
+      override;
+    procedure Paint; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    property Viewport: TFresnelViewport read FViewport write SetViewport;
+    property Layouter: TSimpleFresnelLayouter read FLayouter write SetLayouter;
+    property FontEngine: TFresnelLCLFontEngine read FFontEngine;
+    property Renderer: TFresnelLCLRenderer read FRenderer;
+  end;
+
+  { TFresnelCustomForm }
+
+  TFresnelCustomForm = class(TCustomForm)
+  private
+    FBody: TFresnelBody;
+    FFontEngine: TFresnelLCLFontEngine;
+    FLayouter: TSimpleFresnelLayouter;
+    FRenderer: TFresnelLCLRenderer;
+    FViewport: TFresnelViewport;
+    function GetStylesheet: TStrings;
+    procedure SetStylesheet(const AValue: TStrings);
+  protected
+    procedure Notification(AComponent: TComponent; Operation: TOperation);
+      override;
+    procedure Paint; override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    property Viewport: TFresnelViewport read FViewport;
+    property Layouter: TSimpleFresnelLayouter read FLayouter;
+    property FontEngine: TFresnelLCLFontEngine read FFontEngine;
+    property Renderer: TFresnelLCLRenderer read FRenderer;
+    property Stylesheet: TStrings read GetStylesheet write SetStylesheet;
+    property Body: TFresnelBody read FBody;
+  end;
+
+  { TFresnelForm }
+
+  TFresnelForm = class(TFresnelCustomForm)
+  published
+    property Stylesheet;
+  end;
+
+function CompareFresnelLCLFont(Item1, Item2: Pointer): integer;
+function CompareFresnelFontDescWithLCLFont(Key, Item: Pointer): integer;
+
+implementation
+
+function CompareFresnelLCLFont(Item1, Item2: Pointer): integer;
+var
+  Font1: TFresnelLCLFont absolute Item1;
+  Font2: TFresnelLCLFont absolute Item2;
+begin
+  Result:=CompareText(Font1.Family,Font2.Family);
+  if Result<>0 then exit;
+  if Font1.Size<Font2.Size then
+    exit(-1)
+  else if Font1.Size>Font2.Size then
+    exit(1);
+  Result:=CompareText(Font1.Style,Font2.Style);
+  if Result<>0 then exit;
+  Result:=CompareText(Font1.Weight,Font2.Weight);
+  if Result<>0 then exit;
+  Result:=CompareText(Font1.Variant_,Font2.Variant_);
+  if Result<>0 then exit;
+  Result:=CompareText(Font1.Kerning,Font2.Kerning);
+end;
+
+function CompareFresnelFontDescWithLCLFont(Key, Item: Pointer): integer;
+var
+  Desc: PFresnelFontDesc absolute Key;
+  aFont: TFresnelLCLFont absolute Item;
+begin
+  Result:=CompareText(Desc^.Family,aFont.Family);
+  if Result<>0 then exit;
+  if Desc^.Size<aFont.Size then
+    exit(-1)
+  else if Desc^.Size>aFont.Size then
+    exit(1);
+  Result:=CompareText(Desc^.Style,aFont.Style);
+  if Result<>0 then exit;
+  Result:=CompareText(Desc^.Weight,aFont.Weight);
+  if Result<>0 then exit;
+  Result:=CompareText(Desc^.Variant_,aFont.Variant_);
+  if Result<>0 then exit;
+  Result:=CompareText(Desc^.Kerning,aFont.Kerning);
+end;
+
+{ TFresnelLCLRenderer }
+
+procedure TFresnelLCLRenderer.FillRect(const aColor: TFPColor;
+  const aRect: TFresnelRect);
+begin
+  Canvas.Brush.FPColor:=aColor;
+  Canvas.Brush.Style:=bsSolid;
+  Canvas.FillRect(Rect(floor(aRect.Left),floor(aRect.Top),ceil(aRect.Right),ceil(aRect.Bottom)));
+end;
+
+procedure TFresnelLCLRenderer.Line(const aColor: TFPColor; const x1, y1, x2,
+  y2: TFresnelLength);
+begin
+  Canvas.Pen.FPColor:=aColor;
+  Canvas.Pen.Style:=psSolid;
+  Canvas.Line(round(x1),round(y1),round(x2),round(y2));
+end;
+
+procedure TFresnelLCLRenderer.TextOut(const aLeft, aTop: TFresnelLength;
+  const aFont: IFresnelFont; const aColor: TFPColor; const aText: string
+  );
+var
+  ts: TTextStyle;
+  aFresnelFont: TFresnelLCLFont;
+begin
+  aFresnelFont:=aFont.GetTool as TFresnelLCLFont;
+  Canvas.Font:=aFresnelFont.LCLFont;
+  Canvas.Font.FPColor:=aColor;
+  ts:=Canvas.TextStyle;
+  ts.Opaque:=false;
+  Canvas.TextStyle:=ts;
+  Canvas.TextOut(round(aLeft),round(aTop),aText);
+end;
+
+constructor TFresnelLCLRenderer.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+end;
+
+{ TFresnelCustomForm }
+
+function TFresnelCustomForm.GetStylesheet: TStrings;
+begin
+  Result:=Viewport.Stylesheet;
+end;
+
+procedure TFresnelCustomForm.SetStylesheet(const AValue: TStrings);
+begin
+  Viewport.Stylesheet:=AValue;
+end;
+
+procedure TFresnelCustomForm.Notification(AComponent: TComponent;
+  Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if Operation=opRemove then
+  begin
+    if AComponent=FViewport then
+      FViewport:=nil;
+    if AComponent=FLayouter then
+    begin
+      FLayouter:=nil;
+      if FViewport<>nil then
+        FViewport.Layouter:=nil;
+    end;
+  end;
+end;
+
+procedure TFresnelCustomForm.Paint;
+begin
+  inherited Paint;
+  Renderer.Draw(Viewport);
+end;
+
+constructor TFresnelCustomForm.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FViewport:=TFresnelViewport.Create(nil);
+  FFontEngine:=TFresnelLCLFontEngine.Create(nil);
+  FViewport.FontEngine:=FontEngine;
+  FontEngine.Canvas:=Canvas;
+  FViewport.Name:='Viewport';
+  FLayouter:=TSimpleFresnelLayouter.Create(nil);
+  Layouter.Viewport:=ViewPort;
+  FRenderer:=TFresnelLCLRenderer.Create(nil);
+  FRenderer.Canvas:=Canvas;
+
+  FBody:=TFresnelBody.Create(nil);
+  FBody.Parent:=FViewport;
+end;
+
+destructor TFresnelCustomForm.Destroy;
+begin
+  FreeAndNil(FBody);
+  FreeAndNil(FRenderer);
+  FreeAndNil(FLayouter);
+  FreeAndNil(FViewport);
+  FreeAndNil(FFontEngine);
+  inherited Destroy;
+end;
+
+{ TFresnelLCLControl }
+
+procedure TFresnelLCLControl.SetViewport(const AValue: TFresnelViewport);
+begin
+  if FViewport=AValue then Exit;
+  FViewport:=AValue;
+end;
+
+procedure TFresnelLCLControl.SetLayouter(const AValue: TSimpleFresnelLayouter);
+begin
+  if FLayouter=AValue then Exit;
+  FLayouter:=AValue;
+  if FViewport<>nil then
+    FViewport.Layouter:=FLayouter;
+end;
+
+procedure TFresnelLCLControl.Notification(AComponent: TComponent;
+  Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if Operation=opRemove then
+  begin
+    if AComponent=FViewport then
+      FViewport:=nil;
+    if AComponent=FLayouter then
+    begin
+      FLayouter:=nil;
+      if FViewport<>nil then
+        FViewport.Layouter:=nil;
+    end;
+  end;
+end;
+
+procedure TFresnelLCLControl.Paint;
+begin
+  inherited Paint;
+
+  Renderer.Draw(Viewport);
+end;
+
+constructor TFresnelLCLControl.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FViewport:=TFresnelViewport.Create(nil);
+  FFontEngine:=TFresnelLCLFontEngine.Create(nil);
+  FViewport.FontEngine:=FontEngine;
+  FontEngine.Canvas:=Canvas;
+  FViewport.Name:='Viewport';
+  FLayouter:=TSimpleFresnelLayouter.Create(nil);
+  Layouter.Viewport:=ViewPort;
+  FRenderer:=TFresnelLCLRenderer.Create(nil);
+  FRenderer.Canvas:=Canvas;
+end;
+
+destructor TFresnelLCLControl.Destroy;
+begin
+  FreeAndNil(FRenderer);
+  FreeAndNil(FLayouter);
+  FreeAndNil(FViewport);
+  FreeAndNil(FFontEngine);
+  inherited Destroy;
+end;
+
+{ TFresnelLCLFontEngine }
+
+constructor TFresnelLCLFontEngine.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FFonts:=TAvgLvlTree.Create(@CompareFresnelLCLFont);
+end;
+
+destructor TFresnelLCLFontEngine.Destroy;
+var
+  Node: TAVLTreeNode;
+  aFont: TFresnelLCLFont;
+begin
+  Node:=FFonts.Root;
+  while Node<>nil do
+  begin
+    aFont:=TFresnelLCLFont(Node.Data);
+    Node.Data:=nil;
+    aFont._Release;
+    Node:=Node.Successor;
+  end;
+  FreeAndNil(FFonts);
+  inherited Destroy;
+end;
+
+function TFresnelLCLFontEngine.FindFont(const Desc: TFresnelFontDesc
+  ): TFresnelLCLFont;
+var
+  Node: TAVLTreeNode;
+begin
+  Node:=FFonts.FindKey(@Desc,@CompareFresnelFontDescWithLCLFont);
+  if Node=nil then
+    Result:=nil
+  else
+    Result:=TFresnelLCLFont(Node.Data);
+end;
+
+function TFresnelLCLFontEngine.Allocate(const Desc: TFresnelFontDesc
+  ): IFresnelFont;
+var
+  aFont: TFresnelLCLFont;
+begin
+  aFont:=FindFont(Desc);
+  if aFont<>nil then
+    exit(aFont);
+  aFont:=TFresnelLCLFont.Create;
+  aFont.Engine:=Self;
+  aFont._AddRef;
+  aFont.Family:=Desc.Family;
+  aFont.Kerning:=Desc.Kerning;
+  aFont.Size:=Desc.Size;
+  aFont.Style:=Desc.Style;
+  aFont.Variant_:=Desc.Variant_;
+  aFont.Weight:=Desc.Weight;
+  FFonts.Add(aFont);
+  Result:=aFont;
+end;
+
+function TFresnelLCLFontEngine.TextSize(aFont: TFresnelLCLFont;
+  const aText: string): TPoint;
+var
+  aSize: TSize;
+begin
+  Canvas.Font:=NeedLCLFont(aFont);
+  aSize:=Canvas.TextExtent(aText);
+  Result.X:=aSize.cx;
+  Result.Y:=aSize.cy;
+end;
+
+function TFresnelLCLFontEngine.TextSizeMaxWidth(aFont: TFresnelLCLFont;
+  const aText: string; MaxWidth: integer): TPoint;
+var
+  aSize: TSize;
+begin
+  Canvas.Font:=NeedLCLFont(aFont);
+
+  if LCLIntf.GetTextExtentExPoint(Canvas.Handle, PChar(aText), Length(aText),
+    MaxWidth, nil, nil, aSize) then
+  begin
+    Result.X:=aSize.cx;
+    Result.Y:=aSize.cy;
+  end else begin
+    Result.X:=0;
+    Result.Y:=0;
+  end;
+end;
+
+function TFresnelLCLFontEngine.NeedLCLFont(aFont: TFresnelLCLFont): TFont;
+var
+  aLCLFont: TFont;
+  v: integer;
+  aStyle: TFontStyles;
+begin
+  if aFont.LCLFont=nil then
+  begin
+    aLCLFont:=TFont.Create;
+    aFont.LCLFont:=aLCLFont;
+    case aFont.Size of
+    'xx-small': aLCLFont.Size:=6;
+    'x-small': aLCLFont.Size:=7;
+    'smaller': aLCLFont.Size:=8;
+    'small': aLCLFont.Size:=8;
+    'medium': aLCLFont.Size:=10;
+    'large': aLCLFont.Size:=13;
+    'larger': aLCLFont.Size:=15;
+    'x-large': aLCLFont.Size:=20;
+    'xx-large': aLCLFont.Size:=30;
+    else
+      if TryStrToInt(aFont.Size,v) then
+      begin
+        if v<4 then v:=4;
+        aLCLFont.Size:=v;
+      end;
+    end;
+    aStyle:=[];
+    case aFont.Weight of
+    'bold',
+    'bolder',
+    '500',
+    '600',
+    '700': Include(aStyle,fsBold);
+    end;
+    case aFont.Style of
+    'italic': Include(aStyle,fsItalic);
+    end;
+    aLCLFont.Style:=aStyle;
+  end;
+  Result:=aFont.LCLFont;
+end;
+
+{ TFresnelLCLFont }
+
+destructor TFresnelLCLFont.Destroy;
+begin
+  FreeAndNil(LCLFont);
+  inherited Destroy;
+end;
+
+function TFresnelLCLFont.GetFamily: string;
+begin
+  Result:=Family;
+end;
+
+function TFresnelLCLFont.GetKerning: string;
+begin
+  Result:=Kerning;
+end;
+
+function TFresnelLCLFont.GetSize: string;
+begin
+  Result:=Size;
+end;
+
+function TFresnelLCLFont.GetStyle: string;
+begin
+  Result:=Style;
+end;
+
+function TFresnelLCLFont.GetVariant: string;
+begin
+  Result:=Variant_;
+end;
+
+function TFresnelLCLFont.GetWeight: string;
+begin
+  Result:=Weight;
+end;
+
+function TFresnelLCLFont.TextSize(const aText: string): TFresnelPoint;
+var
+  p: TPoint;
+begin
+  p:=Engine.TextSize(Self,aText);
+  Result.X:=p.X;
+  Result.Y:=p.Y;
+end;
+
+function TFresnelLCLFont.TextSizeMaxWidth(const aText: string;
+  MaxWidth: TFresnelLength): TFresnelPoint;
+var
+  p: TPoint;
+begin
+  p:=Engine.TextSizeMaxWidth(Self,aText,Floor(Max(1,MaxWidth)));
+  Result.X:=p.X;
+  Result.Y:=p.Y;
+end;
+
+function TFresnelLCLFont.GetTool: TObject;
+begin
+  Result:=Self;
+end;
+
+end.
+

+ 148 - 0
src/fresnelrenderer.pas

@@ -0,0 +1,148 @@
+unit FresnelRenderer;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Math, FPImage, FresnelDOM, FresnelControls,
+  FresnelLayouter;
+
+type
+
+  { TFresnelRenderer }
+
+  TFresnelRenderer = class(TComponent)
+  private
+    FSubPixel: boolean;
+  protected
+    procedure FillRect(const aColor: TFPColor; const aRect: TFresnelRect); virtual; abstract;
+    procedure Line(const aColor: TFPColor; const x1, y1, x2, y2: TFresnelLength); virtual; abstract;
+    procedure TextOut(const aLeft, aTop: TFresnelLength; const aFont: IFresnelFont; const aColor: TFPColor; const aText: string); virtual; abstract;
+    procedure MathRoundRect(var r: TFresnelRect);
+    procedure DrawElement(El: TFresnelElement; const dx, dy: TFresnelLength); virtual;
+  public
+    procedure Draw(Viewport: TFresnelViewport); virtual;
+    property SubPixel: boolean read FSubPixel write FSubPixel;
+  end;
+
+implementation
+
+{ TFresnelRenderer }
+
+procedure TFresnelRenderer.MathRoundRect(var r: TFresnelRect);
+begin
+  r.Left:=round(r.Left);
+  r.Right:=round(r.Right);
+  r.Top:=round(r.Top);
+  r.Bottom:=round(r.Bottom);
+end;
+
+procedure TFresnelRenderer.DrawElement(El: TFresnelElement; const dx,
+  dy: TFresnelLength);
+var
+  LNode: TSimpleFresnelLayoutNode;
+  aBackgroundColor, aBorderColor, aCaption: String;
+  aBackgroundColorFP, aBorderColorFP: TFPColor;
+  aLeft, aTop, aRight, aBottom,
+    aMarginLeft, aMarginTop, aMarginRight, aMarginBottom,
+    aBorderLeft, aBorderRight, aBorderTop, aBorderBottom,
+    aPaddingLeft, aPaddingRight, aPaddingTop, aPaddingBottom: TFresnelLength;
+  aBorderBox, aContentBox: TFresnelRect;
+  i: Integer;
+begin
+  //DebugLn(['TFresnelLCLControl.DrawElement ',El.GetPath]);
+  LNode:=TSimpleFresnelLayoutNode(El.LayoutNode);
+  if LNode.SkipRendering then exit;
+
+  if not (El is TFresnelViewport) then
+  begin
+    aLeft:=El.GetComputedCSSLength(fcaLeft,false)+dx;
+    aTop:=El.GetComputedCSSLength(fcaTop,false)+dy;
+    //aWidth:=El.GetComputedCSSLength(fcaWidth,false);
+    //aHeight:=El.GetComputedCSSLength(fcaHeight,false);
+    aRight:=El.GetComputedCSSLength(fcaRight,false);
+    aBottom:=El.GetComputedCSSLength(fcaBottom,false);
+
+    aMarginLeft:=El.GetComputedCSSLength(fcaMarginLeft,false);
+    aMarginRight:=El.GetComputedCSSLength(fcaMarginRight,false);
+    aMarginTop:=El.GetComputedCSSLength(fcaMarginTop,false);
+    aMarginBottom:=El.GetComputedCSSLength(fcaMarginBottom,false);
+
+    aBorderLeft:=El.GetComputedCSSLength(fcaBorderLeftWidth,false);
+    aBorderRight:=El.GetComputedCSSLength(fcaBorderRightWidth,false);
+    aBorderTop:=El.GetComputedCSSLength(fcaBorderTopWidth,false);
+    aBorderBottom:=El.GetComputedCSSLength(fcaBorderBottomWidth,false);
+
+    aPaddingLeft:=El.GetComputedCSSLength(fcaPaddingLeft,false);
+    aPaddingRight:=El.GetComputedCSSLength(fcaPaddingRight,false);
+    aPaddingTop:=El.GetComputedCSSLength(fcaPaddingTop,false);
+    aPaddingBottom:=El.GetComputedCSSLength(fcaPaddingBottom,false);
+
+    aBorderBox.Left:=aLeft+aMarginLeft;
+    aBorderBox.Top:=aTop+aMarginTop;
+    aBorderBox.Right:=aRight-aMarginRight;
+    aBorderBox.Bottom:=aBottom-aMarginBottom;
+    if not SubPixel then
+      MathRoundRect(aBorderBox);
+
+    aContentBox.Left:=aLeft+aMarginLeft+aBorderLeft+aPaddingLeft;
+    aContentBox.Top:=aTop+aMarginTop+aBorderTop+aPaddingTop;
+    aContentBox.Right:=aRight-aMarginRight-aBorderRight-aPaddingRight;
+    aContentBox.Bottom:=aBottom-aMarginBottom-aBorderBottom-aPaddingBottom;
+    if not SubPixel then
+      MathRoundRect(aContentBox);
+
+    //debugln(['TFresnelRenderer.DrawElement ',El.Name,' Border=',dbgs(aBorderBox),' Content=',dbgs(aContentBox)]);
+
+    aBackgroundColor:=El.CSSComputedAttribute[fcaBackgroundColor];
+    if not TryHtmlToFPColor(aBackgroundColor,aBackgroundColorFP) then
+      aBackgroundColorFP:=colTransparent;
+    if aBackgroundColorFP.Alpha<>alphaTransparent then
+    begin
+      FillRect(aBackgroundColorFP,aBorderBox);
+    end;
+
+    aBorderColor:=El.CSSComputedAttribute[fcaBorderColor];
+    if not TryHtmlToFPColor(aBorderColor,aBorderColorFP) then
+      aBorderColorFP:=colTransparent;
+    if aBorderColorFP.Alpha<>alphaTransparent then
+    begin
+      //debugln(['TFresnelRenderer.DrawElement drawing border ',El.Name]);
+      // left border
+      for i:=0 to Ceil(aBorderLeft)-1 do
+        Line(aBorderColorFP,aBorderBox.Left+i,aBorderBox.Top,aBorderBox.Left+i,aBorderBox.Bottom);
+      // right border
+      for i:=0 to ceil(aBorderRight)-1 do
+        Line(aBorderColorFP,aBorderBox.Right-i,aBorderBox.Top,aBorderBox.Right-i,aBorderBox.Bottom);
+      // top border
+      for i:=0 to ceil(aBorderTop)-1 do
+        Line(aBorderColorFP,aBorderBox.Left,aBorderBox.Top+i,aBorderBox.Right,aBorderBox.Top+i);
+      // bottom border
+      for i:=0 to ceil(aBorderBottom)-1 do
+        Line(aBorderColorFP,aBorderBox.Left,aBorderBox.Bottom-i,aBorderBox.Right,aBorderBox.Bottom-i);
+    end;
+
+    if El is TFresnelLabel then
+    begin
+      aCaption:=TFresnelLabel(El).Caption;
+      if aCaption<>'' then
+      begin
+        TextOut(aContentBox.Left,aContentBox.Top,El.Font,colBlack,aCaption);
+      end;
+    end;
+  end;
+
+  for i:=0 to LNode.NodeCount-1 do
+  begin
+    DrawElement(TSimpleFresnelLayoutNode(LNode.Nodes[i]).Element,dx+aLeft,dy+aTop);
+  end;
+end;
+
+procedure TFresnelRenderer.Draw(Viewport: TFresnelViewport);
+begin
+  DrawElement(Viewport,0,0);
+end;
+
+end.
+