瀏覽代碼

layouter: consider overflow

mattias 2 天之前
父節點
當前提交
c6157a9bae
共有 4 個文件被更改,包括 225 次插入148 次删除
  1. 6 25
      src/base/fcl-css/fpcssresolver.pas
  2. 20 11
      src/base/fresnel.dom.pas
  3. 142 107
      src/base/fresnel.layouter.pas
  4. 57 5
      tests/base/TCFlowLayout.pas

+ 6 - 25
src/base/fcl-css/fpcssresolver.pas

@@ -257,7 +257,6 @@ type
     AllValue: TCSSNumericalID;
     Values: TCSSAttributeValueArray; // the resolver sorts them ascending for AttrID, shorthands are already replaced with longhands
     procedure SortValues; virtual; // ascending AttrID
-    procedure SwapValues(Index1, Index2: integer);
     function IndexOf(AttrID: TCSSNumericalID): integer;
     procedure SetComputedValue(AttrID: TCSSNumericalID; const aValue: TCSSString);
     destructor Destroy; override;
@@ -686,6 +685,7 @@ procedure TCSSAttributeValues.SortValues;
 var
   l: SizeInt;
   i, j: Integer;
+  aValue: TCSSAttributeValue;
 begin
   l:=length(Values);
   if l<6 then
@@ -693,7 +693,11 @@ begin
     for i:=0 to l-2 do
       for j:=i+1 to l-1 do
         if Values[i].AttrID>Values[j].AttrID then
-          SwapValues(i,j);
+        begin
+          aValue:=Values[i];
+          Values[i]:=Values[j];
+          Values[j]:=aValue;
+        end;
   end else begin
     //for i:=0 to l-1 do
     //  writeln('TCSSAttributeValues.SortValues ',i,' ',Values[i]<>nil);
@@ -704,29 +708,6 @@ begin
   end;
 end;
 
-procedure TCSSAttributeValues.SwapValues(Index1, Index2: integer);
-var
-  A, B: TCSSAttributeValue;
-  AttrId: TCSSNumericalID;
-  Value: TCSSString;
-  aState: TCSSAttributeValue.TState;
-begin
-  A:=Values[Index1];
-  B:=Values[Index2];
-
-  AttrId:=A.AttrID;
-  A.AttrID:=B.AttrID;
-  B.AttrID:=AttrID;
-
-  Value:=A.Value;
-  A.Value:=B.Value;
-  B.Value:=Value;
-
-  aState:=A.State;
-  A.State:=B.State;
-  B.State:=aState;
-end;
-
 function TCSSAttributeValues.IndexOf(AttrID: TCSSNumericalID): integer;
 var
   l, r, m: Integer;

+ 20 - 11
src/base/fresnel.dom.pas

@@ -41,6 +41,7 @@ type
 const
   FresnelDefaultDPI = 96;
   FresnelDefaultFontSize = 10;
+  FresnelDefaultScrollbarWidth = 12;
   FresnelFontWeightNormal = 400;
 
 type
@@ -1127,8 +1128,8 @@ type
     PaddingTop: TFresnelLength;
     PaddingBottom: TFresnelLength;
     LineHeight: TFresnelLength;
-    CanScrollX: boolean; // overflow-x in hidden, scroll or auto
-    CanScrollY: boolean; // overflow-y in hidden, scroll or auto
+    CanScrollX: boolean; // overflow-x is hidden, scroll or auto
+    CanScrollY: boolean; // overflow-y is hidden, scroll or auto
     GutterVertical: TGutterMode;
     GutterHorizontal: TGutterMode;
     ScrollGutterLeft: TFresnelLength;
@@ -1829,9 +1830,9 @@ type
     property VPApplication: IFresnelVPApplication read FVPApplication;
     property DPI[IsHorizontal: boolean]: TFresnelLength read GetDPI write SetDPI;
 
-    property ScrollbarsAutoHide: boolean read FScrollbarsAutoHide write SetScrollbarsAutoHide;
+    property ScrollbarsAutoHide: boolean read FScrollbarsAutoHide write SetScrollbarsAutoHide; // show only on hover
     property ScrollbarsHaveButtons: boolean read FScrollbarsHaveButtons write SetScrollbarsHaveButtons;
-    property ScrollbarsOverlay: boolean read FScrollbarsOverlay write SetScrollbarsOverlay;
+    property ScrollbarsOverlay: boolean read FScrollbarsOverlay write SetScrollbarsOverlay; // false = allocate gutter space on overflow
     property ScrollbarsWidth[IsHorizontal: boolean]: TFresnelLength read GetScrollbarsWidth write SetScrollbarsWidth;
     property ScrollbarsThinWidth[IsHorizontal: boolean]: TFresnelLength read GetScrollbarsThinWidth write SetScrollbarsThinWidth;
     property ScrollbarsColors[Col: TFresnelScrollbarColor]: TFPColor read GetScrollbarsColors write SetScrollbarsColors;
@@ -1866,6 +1867,7 @@ function FontVariantLigaturesToStr(const FVLigatures: TFresnelCSSFontVarLigature
 function FontVariantNumericsToStr(const FVNumerics: TFresnelCSSFontVarNumerics): string;
 
 function dbgs(const c: TFPColor): string; overload;
+function dbgs(m: TFresnelLayoutNode.TGutterMode): string; overload;
 
 implementation
 
@@ -2061,6 +2063,12 @@ begin
     Result:='$A'+HexStr(c.Alpha,4)+'R'+HexStr(c.Red,4)+'G'+HexStr(c.Green,4)+'B'+HexStr(c.Blue,4);
 end;
 
+function dbgs(m: TFresnelLayoutNode.TGutterMode): string;
+begin
+  Result:='';
+  str(m,Result);
+end;
+
 { TFresnelCSSRegistry }
 
 function TFresnelCSSRegistry.CheckDisplay(Resolver: TCSSBaseResolver): boolean;
@@ -7763,10 +7771,10 @@ begin
   FHeight:=600;
   FDPI[false]:=FresnelDefaultDPI;
   FDPI[true]:=FresnelDefaultDPI;
-  FScrollbarsWidth[False]:=12;
-  FScrollbarsWidth[true]:=12;
-  FScrollbarsThinWidth[False]:=6;
-  FScrollbarsThinWidth[true]:=6;
+  FScrollbarsWidth[False]:=FresnelDefaultScrollbarWidth;
+  FScrollbarsWidth[true]:=FresnelDefaultScrollbarWidth;
+  FScrollbarsThinWidth[False]:=TFresnelLength(FresnelDefaultScrollbarWidth)/2;
+  FScrollbarsThinWidth[true]:=TFresnelLength(FresnelDefaultScrollbarWidth)/2;
   c:=colDkGray;
   FScrollbarsColor[vscThumbHover]:=c;
   c.Alpha:=$8000;
@@ -8789,9 +8797,9 @@ begin
     exit(true);
   end;
 
-  if ComputedOverflowX in [CSSRegistry.kwVisible,CSSRegistry.kwClip] then
+  if not (ComputedOverflowX in [CSSRegistry.kwVisible,CSSRegistry.kwClip]) then
     exit(true);
-  if ComputedOverflowY in [CSSRegistry.kwVisible,CSSRegistry.kwClip] then
+  if not (ComputedOverflowY in [CSSRegistry.kwVisible,CSSRegistry.kwClip]) then
     exit(true);
 
   if ComputedFloat<>CSSRegistry.kwNone then
@@ -9344,7 +9352,7 @@ var
   AttrDesc: TFresnelCSSAttrDesc;
 begin
   {$IFDEF VerboseCSSAttr}
-  writeln('TFresnelElement.SetComputedCSSString ',Name,' ',Attr,': ',Value);
+  writeln('TFresnelElement.SetComputedCSSString ',DbgSName(Self),' ',Name,' ',Attr,': ',Value);
   {$ENDIF}
   AttrDesc:=CSSRegistry.FresnelAttrs[Attr];
   if FCSSValues=nil then
@@ -10407,6 +10415,7 @@ begin
     end else begin
       aValue.Value:=AttrDesc.InitialValue;
     end;
+    //writeln('TFresnelElement.ComputeCSSValues ',DbgSName(Self),' ',AttrDesc.Name,'=',aValue.Value);
   end;
 
   ComputeBaseAttributes;

+ 142 - 107
src/base/fresnel.layouter.pas

@@ -102,10 +102,11 @@ type
   protected
     FAbsoluteItems: TFPList; // list of TLineItem
     FLineItems: TFPList; // list of TLineItem
+    FOverflow: TFresnelRect; // current overflow
     function PlaceAbsoluteItem(ChildNode: TUsedLayoutNode; aMode: TFresnelLayoutMode;
                   aMaxWidth, aMaxHeight: TFresnelLength; const DefaultPos: TFresnelPoint;
                   Commit: boolean): TFreIntrinsicContentSize; virtual;
-    procedure PlaceLineItems; virtual;
+    procedure PlaceLineItems(Commit: boolean); virtual;
     function AddAbsoluteItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass): TLineItem; virtual;
     function AddLineItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass): TLineItem; virtual;
     procedure ClearAbsoluteItems; virtual;
@@ -128,8 +129,6 @@ type
       end;
 
   protected
-    // current overflow
-    FOverflow: TFresnelRect;
     // current line
     FLineBorderBoxHeight: TFresnelLength;
     FLineBorderBoxLeft: TFresnelLength; // border box position of last element relative to parent ContentBox
@@ -145,9 +144,8 @@ type
     FLastLineMarginBottom: TFresnelLength;
     procedure StartLine; virtual;
     procedure EndLine(Commit: boolean; aMode: TFresnelLayoutMode); virtual;
-    procedure PlaceLineItems; override;
+    procedure PlaceLineItems(Commit: boolean); override;
   public
-    procedure DeductUsedLengths(NoChildren: boolean); override;
     function ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength; Commit:
       boolean): TFreIntrinsicContentSize; override;
     function MergeMargins(a, b: TFresnelLength): TFresnelLength;
@@ -189,7 +187,6 @@ type
       var NewContentSize: TFreIntrinsicContentSize); virtual;
     procedure FlexLineCrossDirection(Commit: boolean;
       var NewContentSize: TFreIntrinsicContentSize); virtual;
-    procedure PlaceLineItems; override;
     procedure ComputeChildAttributes(Item: TLineItem; El: TFresnelElement); override;
   public
     procedure Init; override;
@@ -305,7 +302,7 @@ begin
       Size:=GetIntrinsicContentSize(flmMax,MaxWidth,MaxHeight);
       Node.ApplyScrollSize(Size);
       {$IFDEF VerboseFresnelScrolling}
-      writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" IntrinsicContentSize: Size: ',Size.ToString,' Gutter:L=',FloatToCSSStr(Node.ScrollGutterLeft),',R=',FloatToCSSStr(Node.ScrollGutterRight),',T=',FloatToCSSStr(Node.ScrollGutterTop),',B=',FloatToCSSStr(Node.ScrollGutterBottom));
+      writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" IntrinsicContentSize: Size(',Size.ToString,') Gutter:L=',FloatToCSSStr(Node.ScrollGutterLeft),',R=',FloatToCSSStr(Node.ScrollGutterRight),',T=',FloatToCSSStr(Node.ScrollGutterTop),',B=',FloatToCSSStr(Node.ScrollGutterBottom));
       {$ENDIF}
     end;
 
@@ -517,15 +514,18 @@ var
 
 var
   GutterVertical, GutterHorizontal: TFresnelLayoutNode.TGutterMode;
-  HasMaxWidth, HasMaxHeight: Boolean;
+  HasMaxWidth, HasMaxHeight, NeedCompute: Boolean;
   aClientHeight, aClientWidth: TFresnelLength;
 
-  procedure CheckOverflowX;
+  procedure CheckOverflowX(Limit: boolean);
   begin
-    if Node.CanScrollX and HasMaxWidth and (Result.Width>aMaxWidth) then
+    if not Node.CanScrollX then exit;
+    if not HasMaxWidth then exit;
+    if Result.Width>aMaxWidth then
     begin
-      // width does not fit and scrolling allowed
-      Result.Width:=aMaxWidth;
+      // width (or overflow) does not fit and scrolling allowed
+      if Limit then
+        Result.Width:=aMaxWidth;
       if GutterHorizontal=gmAuto then
       begin
         // auto horizontal gutter needed
@@ -542,12 +542,15 @@ var
     end;
   end;
 
-  procedure CheckOverflowY;
+  procedure CheckOverflowY(Limit: boolean);
   begin
-    if Node.CanScrollY and HasMaxHeight and (Result.Height>aMaxHeight) then
+    if not Node.CanScrollY then exit;
+    if not HasMaxHeight then exit;
+    if (Result.Height>aMaxHeight) then
     begin
-      // height does not fit and scrolling allowed
-      Result.Height:=aMaxHeight;
+      // height (or overflow) does not fit and scrolling allowed
+      if Limit then
+        Result.Height:=aMaxHeight;
       if GutterVertical=gmAuto then
       begin
         // auto vertical gutter needed
@@ -564,8 +567,6 @@ var
     end;
   end;
 
-var
-  OldNeedVert: Boolean;
 begin
   case aMode of
     flmMinWidth:  aMaxWidth:=NaN;
@@ -616,24 +617,51 @@ begin
     Compute(aMode,aClientWidth,aClientHeight,true);
     case aMode of
     flmMinWidth:
-      CheckOverflowY;
+      CheckOverflowY(true);
     flmMinHeight:
-      CheckOverflowX;
+      CheckOverflowX(true);
     flmMax:
       begin
         // maximize width
-        CheckOverflowX;
-        CheckOverflowY;
-        if HasMaxWidth and (Result.Width>aMaxWidth) and Node.CanScrollX then
+        //writeln('AAA3.1 ',DbgSName(Node.Element),' flmMax W=',FloatToCSSStr(Result.Width),' MaxW=',FloatToCSSStr(aMaxWidth),' H=',FloatToCSSStr(Result.Height),' MaxH=',FloatToCSSStr(aMaxHeight));
+
+        CheckOverflowX(false);
+        CheckOverflowY(false);
+        if (GutterHorizontal<>Node.GutterHorizontal) then
         begin
-          // width no longer fits -> compute again
-          OldNeedVert:=Result.NeedGutterVertical;
+          if (GutterVertical<>Node.GutterVertical) then
+          begin
+            // both gutters were auto added
+            NeedCompute:=true;
+          end else begin
+            // only horizontal gutter was auto added
+            NeedCompute:=HasMaxHeight and (Result.Height>aMaxHeight);
+          end;
+        end else begin
+          if (GutterVertical<>Node.GutterVertical) then
+          begin
+            // only vertical gutter was auto added
+            NeedCompute:=HasMaxWidth and (Result.Width>aMaxWidth);
+          end else begin
+            // no gutter was auto added
+            NeedCompute:=false;
+          end;
+        end;
+        if NeedCompute then begin
+          // auto gutter added -> compute again
+          //writeln('AAA3.2 ',DbgSName(Node.Element),' flmMax a gutter was added W=',FloatToCSSStr(Result.Width),' MaxW=',FloatToCSSStr(aMaxWidth),' H=',FloatToCSSStr(Result.Height),' MaxH=',FloatToCSSStr(aMaxHeight));
           Compute(flmMax,aClientWidth,aClientHeight,true);
-          Result.NeedGutterHorizontal:=true;
-          Result.NeedGutterVertical:=OldNeedVert;
-          CheckOverflowX;
-          CheckOverflowY;
+          Result.NeedGutterHorizontal:=GutterHorizontal<>Node.GutterHorizontal;
+          Result.NeedGutterVertical:=GutterVertical<>Node.GutterVertical;
+          CheckOverflowX(true);
+          CheckOverflowY(true);
         end;
+        // limit
+        if HasMaxWidth and Node.CanScrollX and (Result.Width>aMaxWidth) then
+          Result.Width:=aMaxWidth;
+        if HasMaxHeight and Node.CanScrollY and (Result.Height>aMaxHeight) then
+          Result.Height:=aMaxHeight;
+        //writeln('AAA3.3 ',DbgSName(Node.Element),' flmMax Result W=',FloatToCSSStr(Result.Width),' MaxW=',FloatToCSSStr(aMaxWidth),' H=',FloatToCSSStr(Result.Height),' MaxH=',FloatToCSSStr(aMaxHeight));
       end;
     end;
   end else begin
@@ -995,6 +1023,48 @@ begin
   end else begin
     // position is static, relative or sticky
     // -> position will be computed
+
+    if Element.ComputedDisplayOutside=CSSRegistry.kwBlock then
+    begin
+      // a block element with auto width fills the container
+      if Element.ComputedWritingMode=CSSRegistry.kwHorizontalTB then
+      begin
+        if not IsNan(Width) then exit;
+
+        // a block fills the whole container width
+        if Container.LayoutNode.GutterVertical=gmAuto then
+        begin
+          // container has auto vertical scrollbar gutter
+          //-> block width cannot be duduced here, this is handled in TFLFlowLayouter.ComputeLayoutContent
+          exit;
+        end;
+
+        aContainerWidth:=Element.GetContainerContentWidth(true);
+        if not IsNan(aContainerWidth) then
+        begin
+          Width:=aContainerWidth-MarginLeft-BorderLeft-PaddingLeft
+                                -MarginRight-BorderRight-PaddingRight;
+          ApplyMinMaxWidth;
+        end;
+      end else begin
+        if not IsNan(Height) then exit;
+        // a block fills the whole container height
+        if Container.LayoutNode.GutterHorizontal=gmAuto then
+        begin
+          // container has auto horizontal scrollbar gutter
+          //-> height cannot be duduced here, this is handled in TFLFlowLayouter.ComputeLayoutContent
+          exit;
+        end;
+        aContainerHeight:=Element.GetContainerContentHeight(true);
+        if not IsNan(aContainerHeight) then
+        begin
+          Height:=aContainerHeight-MarginTop-BorderTop-PaddingTop
+                                -MarginBottom-BorderBottom-PaddingBottom;
+          ApplyMinMaxHeight;
+        end;
+      end;
+    end;
+
   end;
 
   if Layouter<>nil then
@@ -1019,7 +1089,7 @@ end;
 
 { TFLLineLayouter }
 
-procedure TFLLineLayouter.PlaceLineItems;
+procedure TFLLineLayouter.PlaceLineItems(Commit: boolean);
 var
   i: Integer;
   Item: TLineItem;
@@ -1079,19 +1149,29 @@ begin
     aClientBox.Right:=aBorderBox.Right-ChildNode.PaddingRight;
     aClientBox.Bottom:=aBorderBox.Bottom-ChildNode.PaddingBottom;
 
-    ChildEl.UsedBorderBox:=aBorderBox;
-    ChildEl.UsedContentBox:=aContentBox;
-    ChildEl.UsedClientBox:=aClientBox;
-    ChildNode.Left:=NewLeft;
-    ChildNode.Top:=NewTop;
-    ChildNode.Right:=aBorderBox.Right+ChildNode.MarginRight;
-    ChildNode.Bottom:=aBorderBox.Bottom+ChildNode.MarginBottom;
-    ChildNode.Width:=aContentBox.Width;
-    ChildNode.Height:=aContentBox.Height;
+    //writeln('AAA2 TFLLineLayouter.PlaceLineItems ',DbgSName(ChildEl),' Overflow=',Item.Overflow.ToString,' ContentBox=',aContentBox.ToString);
+    FOverflow.Left:=min(FOverflow.Left,aContentBox.Left+Item.Overflow.Left);
+    FOverflow.Top:=min(FOverflow.Top,aContentBox.Top+Item.Overflow.Top);
+    FOverflow.Right:=max(FOverflow.Right,aContentBox.Left+Item.Overflow.Right);
+    FOverflow.Right:=max(FOverflow.Right,aBorderBox.Right+ChildNode.MarginRight);
+    FOverflow.Bottom:=max(FOverflow.Bottom,aContentBox.Top+Item.Overflow.Bottom);
+    FOverflow.Bottom:=max(FOverflow.Bottom,aBorderBox.Bottom+ChildNode.MarginBottom);
 
-    {$IFDEF VerboseFresnelPlacing}
-    writeln('TFLFlowLayouter.PlaceLineItems '+ChildEl.GetPath+' BorderBox='+ChildEl.UsedBorderBox.ToString);
-    {$ENDIF}
+    if Commit then
+    begin
+      ChildEl.UsedBorderBox:=aBorderBox;
+      ChildEl.UsedContentBox:=aContentBox;
+      ChildEl.UsedClientBox:=aClientBox;
+      ChildNode.Left:=NewLeft;
+      ChildNode.Top:=NewTop;
+      ChildNode.Right:=aBorderBox.Right+ChildNode.MarginRight;
+      ChildNode.Bottom:=aBorderBox.Bottom+ChildNode.MarginBottom;
+      ChildNode.Width:=aContentBox.Width;
+      ChildNode.Height:=aContentBox.Height;
+      {$IFDEF VerboseFresnelPlacing}
+      writeln('TFLFlowLayouter.PlaceLineItems '+ChildEl.GetPath+' BorderBox='+ChildEl.UsedBorderBox.ToString);
+      {$ENDIF}
+    end;
   end;
 end;
 
@@ -1114,8 +1194,12 @@ begin
 
   {$IFDEF VerboseFresnelPlacing}
   //if ChildEl.Name='Div1' then
-    writeln('TFLLineLayouter.PlaceAbsoluteItem ',ChildEl.GetPath,' ',aMode,' Commit=',Commit,' Left=',FloatToCSSStr(ChildNode.Left),',Top=',FloatToCSSStr(ChildNode.Top),',Right=',FloatToCSSStr(ChildNode.Right),',Bottom=',FloatToCSSStr(ChildNode.Bottom),' Width=',FloatToCSSStr(ChildNode.Width),' Height=',FloatToCSSStr(ChildNode.Height),' Default=',FloatToCSSStr(DefaultPos.X),',',FloatToCSSStr(DefaultPos.Y));
-    writeln('TFLLineLayouter.PlaceAbsoluteItem ',CSSRegistry.Keywords[ChildEl.ComputedDisplayOutside],' ',CSSRegistry.Keywords[ChildEl.ComputedDisplayInside],' Style=',ChildEl.Style);
+    writeln('TFLLineLayouter.PlaceAbsoluteItem ',ChildEl.GetPath,' ',aMode,' Commit=',Commit,
+      ' Left=',FloatToCSSStr(ChildNode.Left),',Top=',FloatToCSSStr(ChildNode.Top),',Right=',FloatToCSSStr(ChildNode.Right),',Bottom=',FloatToCSSStr(ChildNode.Bottom),
+      ' Width=',FloatToCSSStr(ChildNode.Width),' Height=',FloatToCSSStr(ChildNode.Height),
+      ' Default=',FloatToCSSStr(DefaultPos.X),',',FloatToCSSStr(DefaultPos.Y),
+      ' MaxW=',FloatToCSSStr(aMaxWidth),' MaxH=',FloatToCSSStr(aMaxHeight));
+    writeln('TFLLineLayouter.PlaceAbsoluteItem ',CSSRegistry.Keywords[ChildEl.ComputedDisplayOutside],' ',CSSRegistry.Keywords[ChildEl.ComputedDisplayInside],' Style="',ChildEl.Style,'"');
   {$ENDIF}
   FrameLeft:=ChildNode.MarginLeft+ChildNode.BorderLeft+ChildNode.PaddingLeft;
   FrameRight:=ChildNode.PaddingRight+ChildNode.BorderRight+ChildNode.MarginRight;
@@ -1338,15 +1422,16 @@ var
   El: TFresnelElement;
   IsInline: Boolean;
 begin
+  if Commit then ;
   if FLineItems.Count=0 then
     exit;
   FLineBorderBoxTop:=FLastLineBorderBoxBottom
             +Max(FLastLineMarginBottom,FLineMarginTop); // margin collapsing
   FLineBorderBoxBottom:=FLineBorderBoxTop+FLineBorderBoxHeight;
 
-  if Commit or (aMode=flmMax) then
+  if aMode=flmMax then
   begin
-    PlaceLineItems;
+    PlaceLineItems(Commit);
     if FAbsoluteItems<>nil then
     begin
       // set default top position of absolute blocks below line
@@ -1373,69 +1458,25 @@ begin
   //writeln('TFLFlowLayouter.EndLine ',Node.Element.Name,' FBorderBox=',FBorderBox.X,',',FBorderBox.Y);
 end;
 
-procedure TFLFlowLayouter.PlaceLineItems;
+procedure TFLFlowLayouter.PlaceLineItems(Commit: boolean);
 var
-  BFCNode: TFlowItem;
+  Item: TFlowItem;
   i: Integer;
   ChildNode: TUsedLayoutNode;
 begin
   for i:=0 to FLineItems.Count-1 do
   begin
-    BFCNode:=TFlowItem(FLineItems[i]);
-    ChildNode:=BFCNode.Node;
+    Item:=TFlowItem(FLineItems[i]);
+    ChildNode:=Item.Node;
     //ChildEl:=ChildNode.Element;
 
-    BFCNode.StaticTop:=FLineBorderBoxTop-ChildNode.MarginTop;
-    //writeln('TFLFlowLayouter.PlaceLineNodes ',i,' ',ChildEl.GetPath,' ',FloatToCSSStr(BFCNode.Left),' ',FloatToCSSStr(ChildTop),' BFCNode.Height=',FloatToCSSStr(BFCNode.ContentBoxHeight));
-
-    FOverflow.Left:=min(FOverflow.Left,BFCNode.Overflow.Left+BFCNode.StaticLeft);
-    FOverflow.Top:=min(FOverflow.Top,BFCNode.Overflow.Top+BFCNode.StaticTop);
-    FOverflow.Right:=max(FOverflow.Right,BFCNode.Overflow.Right+BFCNode.StaticLeft);
-    FOverflow.Bottom:=max(FOverflow.Bottom,BFCNode.Overflow.Bottom+BFCNode.StaticTop);
+    Item.StaticTop:=FLineBorderBoxTop-ChildNode.MarginTop;
+    //writeln('TFLFlowLayouter.PlaceLineNodes ',i,' ',ChildEl.GetPath,' ',FloatToCSSStr(Item.Left),' ',FloatToCSSStr(ChildTop),' BFCNode.Height=',FloatToCSSStr(Item.ContentBoxHeight));
   end;
 
   inherited;
 end;
 
-procedure TFLFlowLayouter.DeductUsedLengths(NoChildren: boolean);
-var
-  aContainerWidth, aContainerHeight: TFresnelLength;
-begin
-  inherited DeductUsedLengths(NoChildren);
-
-  with Node do
-  begin
-    if (Element.ComputedDisplayOutside=CSSRegistry.kwBlock)
-        and (Element.ComputedPosition in [CSSRegistry.kwStatic,CSSRegistry.kwRelative,CSSRegistry.kwSticky])
-        then
-    begin
-      // a block element with auto width fills the container
-      if Element.ComputedWritingMode=CSSRegistry.kwHorizontalTB then
-      begin
-        if not IsNan(Width) then exit;
-        // a block fills the whole container width
-        aContainerWidth:=Element.GetContainerContentWidth(true);
-        if not IsNan(aContainerWidth) then
-        begin
-          Width:=aContainerWidth-MarginLeft-BorderLeft-PaddingLeft
-                                -MarginRight-BorderRight-PaddingRight;
-          ApplyMinMaxWidth;
-        end;
-      end else begin
-        if not IsNan(Height) then exit;
-        // a block fills the whole container height
-        aContainerHeight:=Element.GetContainerContentHeight(true);
-        if not IsNan(aContainerHeight) then
-        begin
-          Height:=aContainerHeight-MarginTop-BorderTop-PaddingTop
-                                -MarginBottom-BorderBottom-PaddingBottom;
-          ApplyMinMaxHeight;
-        end;
-      end;
-    end;
-  end;
-end;
-
 function TFLFlowLayouter.ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth,
   aMaxHeight: TFresnelLength; Commit: boolean): TFreIntrinsicContentSize;
 var
@@ -1471,7 +1512,7 @@ begin
   Result:=default(TFreIntrinsicContentSize);
   FOverflow:=default(TFresnelRect);
 
-  //writeln('TFLFlowLayouter.ComputeLayoutContent ',Node.Element.GetPath,' ',aMode,' MaxWidth=',FloatToCSSStr(aMaxWidth),' MaxHeight=',FloatToCSSStr(aMaxHeight),' Commit=',Commit);
+  //writeln('AAA5 TFLFlowLayouter.ComputeLayoutContent ',Node.Element.GetPath,' ',aMode,' MaxWidth=',FloatToCSSStr(aMaxWidth),' MaxHeight=',FloatToCSSStr(aMaxHeight),' Commit=',Commit);
 
   // ToDo: line-height
   // ToDo: baseline
@@ -1502,7 +1543,7 @@ begin
     // skip position: absolute and fixed
     if ChildEl.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
     begin
-      if Commit or (aMode=flmMax) then
+      if aMode=flmMax then
       begin
         AbsItem:=AddAbsoluteItem(ChildNode,TLineItem);
         if (FLineItems=nil) or (FLineItems.Count=0) then
@@ -1566,7 +1607,7 @@ begin
 
     //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.GetPath,' Commit=',Commit,' ChildWidth=',FloatToStr(ChildWidth),' ChildHeight=',FloatToStr(ChildHeight));
 
-    if Commit and (not IsInline) and IsNan(ChildWidth) and (not IsNan(CurSpace)) then
+    if (aMode=flmMax) and (not IsInline) and IsNan(ChildWidth) and (not IsNan(CurSpace)) then
     begin
       // a block element expands the full line
       //writeln('TFLFlowLayouter.ComputeLayoutContent BLOCK FULL LINE: ',ChildEl.GetPath,' CurSpace=',FloatToStr(CurSpace),' ChildPadBorderX=',FloatToStr(ChildPadBorderX));
@@ -1689,7 +1730,7 @@ begin
   end;
   EndLine(Commit,aMode);
 
-  if (FAbsoluteItems<>nil) and (Commit or (aMode=flmMax)) then
+  if (FAbsoluteItems<>nil) and (aMode=flmMax) then
   begin
     // place absolute items
     for ChildIndex:=0 to FAbsoluteItems.Count-1 do
@@ -1756,8 +1797,7 @@ begin
   if FLineItems.Count=0 then exit;
   FlexLineMainDirection(MaxMainSize,MainGap,Commit,NewContentSize);
   FlexLineCrossDirection(Commit,NewContentSize);
-  if Commit then
-    PlaceLineItems;
+  PlaceLineItems(Commit);
   ClearLineItems;
 end;
 
@@ -2065,11 +2105,6 @@ begin
     NewContentSize.Width:=Max(NewContentSize.Width,MaxSize);
 end;
 
-procedure TFLFlexLayouter.PlaceLineItems;
-begin
-  inherited;
-end;
-
 procedure TFLFlexLayouter.ComputeChildAttributes(Item: TLineItem;
   El: TFresnelElement);
 var

+ 57 - 5
tests/base/TCFlowLayout.pas

@@ -24,8 +24,9 @@ type
     procedure TestFL_PositionAbsolute_DivDefaultPosBehindRelative;
 
     // scroll
-    procedure TestFL_PositionAbsolute_Left_ScrollWidth;
-    procedure TestFL_Viewport_PositionAbsolute;
+    procedure TestFL_Scrollwidth_PositionAbsolute_Left;
+    procedure TestFL_ScrollWidthHeight_Viewport_PositionAbsolute;
+    procedure TestFL_Scrollbars_Viewport_Div;
     procedure TestFL_OverflowSizeNested;
     // todo procedure TestPositionAbsolute_DivTop100Percent;
 
@@ -538,7 +539,7 @@ begin
   AssertEquals('Div1.UsedBorderBox.Top',23,Div1.UsedBorderBox.Top);
 end;
 
-procedure TTestFlowLayout.TestFL_PositionAbsolute_Left_ScrollWidth;
+procedure TTestFlowLayout.TestFL_Scrollwidth_PositionAbsolute_Left;
 var
   Body: TBody;
   Div1: TDiv;
@@ -574,7 +575,7 @@ begin
   AssertEquals('Body.GetComputedString(fcaOverflow)','auto',Body.GetComputedString(fcaOverflow));
   AssertEquals('Body.UsedContentBox.Width',100,Body.UsedContentBox.Width);
   AssertEquals('Body.UsedBorderBox.Width',146,Body.UsedBorderBox.Width); // width+padding
-  AssertEquals('Body.UsedBorderBox.Height',46,Body.UsedBorderBox.Height); // padding is bigger than Div1
+  AssertEquals('Body.UsedBorderBox.Height',46,Body.UsedBorderBox.Height); // 46 = 2 * 23px padding
 
   if Div1.LayoutNode.Container<>Body then
     Fail('Div1.LayoutNode.Container<>Body');
@@ -587,7 +588,7 @@ begin
   AssertEquals('Body.ScrollHeight',34,Body.ScrollHeight);
 end;
 
-procedure TTestFlowLayout.TestFL_Viewport_PositionAbsolute;
+procedure TTestFlowLayout.TestFL_ScrollWidthHeight_Viewport_PositionAbsolute;
 var
   Div1: TDiv;
   VPWidth, VPHeight: TFresnelLength;
@@ -654,6 +655,57 @@ begin
   AssertEquals('Left Viewport.ScrollHeight',VPHeight,Viewport.ScrollHeight);
 end;
 
+procedure TTestFlowLayout.TestFL_Scrollbars_Viewport_Div;
+const
+  BarW = 10;
+var
+  Div1: TDiv;
+  VPWidth, VPHeight: TFresnelLength;
+begin
+  Div1:=TDiv.Create(Viewport);
+  Div1.Name:='Div1';
+  Div1.Parent:=Viewport;
+
+  Viewport.ScrollbarsAutoHide:=false;
+  Viewport.ScrollbarsOverlay:=false;
+  Viewport.ScrollbarsWidth[true]:=BarW;
+  Viewport.ScrollbarsWidth[false]:=BarW;
+  Viewport.Stylesheet.Text:=LinesToStr([
+    'div {',
+    '  margin: 2px;',
+    '  border: 1px;',
+    '  padding: 7px;',
+    '}']);
+
+  VPWidth:=Viewport.Width;
+  VPHeight:=Viewport.Height;
+
+  // Test: div1 fits into viewport, no scrollbars needed
+  Div1.Style:='height: 10px;';
+  Viewport.Draw;
+  AssertEquals('Viewport.GetComputedString(fcaOverflow)','auto',Viewport.GetComputedString(fcaOverflow));
+  AssertEquals('A Viewport.Width',VPWidth,Viewport.Width);
+  AssertEquals('A Viewport.Height',VPHeight,Viewport.Height);
+  AssertEquals('A Viewport.LayoutNode.ScrollGutterRight',0,Viewport.LayoutNode.ScrollGutterRight);
+  AssertEquals('A Viewport.LayoutNode.ScrollGutterBottom',0,Viewport.LayoutNode.ScrollGutterBottom);
+  AssertEquals('A Viewport.ScrollWidth',VPWidth,Viewport.ScrollWidth);
+  AssertEquals('A Viewport.ScrollHeight',VPHeight,Viewport.ScrollHeight);
+  AssertEquals('A Div1.Height',26,Div1.UsedBorderBox.Height);
+  AssertEquals('A Div1.Width',VPWidth-4,Div1.UsedBorderBox.Width);
+
+  // Test: div1 does not fit vertically into viewport, needs vertical scrollbar
+  Div1.Style:='height: '+FloatToCSSPx(VPHeight);
+  Viewport.Draw;
+  AssertEquals('A Viewport.Width',VPWidth,Viewport.Width);
+  AssertEquals('A Viewport.Height',VPHeight,Viewport.Height);
+  AssertEquals('A Viewport.LayoutNode.ScrollGutterRight',BarW,Viewport.LayoutNode.ScrollGutterRight);
+  AssertEquals('A Viewport.LayoutNode.ScrollGutterBottom',0,Viewport.LayoutNode.ScrollGutterBottom);
+  AssertEquals('A Viewport.ScrollWidth',VPWidth-BarW,Viewport.ScrollWidth);
+  AssertEquals('A Viewport.ScrollHeight',VPHeight+20,Viewport.ScrollHeight);
+  AssertEquals('A Div1.Height',VPHeight+16,Div1.UsedBorderBox.Height);
+  AssertEquals('A Div1.Width',VPWidth-4-BarW,Div1.UsedBorderBox.Width);
+end;
+
 procedure TTestFlowLayout.TestFL_OverflowSizeNested;
 var
   Div1, Div2, Div3, Div4: TDiv;