Browse Source

* Refactoring: move report layouting to separate class, so it can be overridden

git-svn-id: trunk@37233 -
michael 8 years ago
parent
commit
396d1b823c
1 changed files with 2498 additions and 2364 deletions
  1. 2498 2364
      packages/fcl-report/src/fpreport.pp

+ 2498 - 2364
packages/fcl-report/src/fpreport.pp

@@ -93,6 +93,8 @@ type
   TFPReportCustomGroupHeaderBand = class;
   TFPReportCustomGroupHeaderBand = class;
   TFPReportExporter       = class;
   TFPReportExporter       = class;
   TFPReportTextAlignment  = class;
   TFPReportTextAlignment  = class;
+  TFPReportLayouter       = Class;
+
   TBandList = class;
   TBandList = class;
 
 
   TFPReportElementClass   = class of TFPReportElement;
   TFPReportElementClass   = class of TFPReportElement;
@@ -1327,7 +1329,6 @@ type
     procedure BuiltinGetPageNoPerDesignerPage(var Result: TFPExpressionResult; const Args: TExprParameterArray);
     procedure BuiltinGetPageNoPerDesignerPage(var Result: TFPExpressionResult; const Args: TExprParameterArray);
     procedure BuiltinGetPageCount(var Result: TFPExpressionResult; const Args: TExprParameterArray);
     procedure BuiltinGetPageCount(var Result: TFPExpressionResult; const Args: TExprParameterArray);
     { checks if children are visble, removes children if needed, and recalc Band.Layout bounds }
     { checks if children are visble, removes children if needed, and recalc Band.Layout bounds }
-    procedure RecalcBandLayout(ABand: TFPReportCustomBand);
     procedure EmptyRTObjects;
     procedure EmptyRTObjects;
     procedure ClearDataBandLastTextValues(ABand: TFPReportCustomBandWithData);
     procedure ClearDataBandLastTextValues(ABand: TFPReportCustomBandWithData);
     procedure ProcessAggregates(const APageIdx: integer; const AData: TFPReportData);
     procedure ProcessAggregates(const APageIdx: integer; const AData: TFPReportData);
@@ -1349,6 +1350,7 @@ type
     function CreateVariables: TFPReportVariables; virtual;
     function CreateVariables: TFPReportVariables; virtual;
     function CreateImages: TFPReportImages; virtual;
     function CreateImages: TFPReportImages; virtual;
     function CreateReportData: TFPReportDataCollection; virtual;
     function CreateReportData: TFPReportDataCollection; virtual;
+    function CreateLayouter : TFPReportLayouter; virtual;
 
 
     procedure RestoreDefaultVariables; virtual;
     procedure RestoreDefaultVariables; virtual;
     procedure DoPrepareReport; virtual;
     procedure DoPrepareReport; virtual;
@@ -1413,6 +1415,102 @@ type
   end;
   end;
 
 
 
 
+  { TFPReportLayouter }
+
+  TFPReportLayouter = Class(TComponent)
+  Private
+    FMyReport: TFPCustomReport;
+    FPageIdx: integer;
+    FNewPage: boolean;  // indicates if a new ReportPage needs to be created - used if DataBand spans multiple pages for example
+    FNewColumn: boolean;
+    FNewGroupHeader: boolean;
+    FLastDsgnDataBand: TFPReportCustomDataBand;
+    FSpaceLeft: TFPReportUnits;
+    FLastYPos: TFPReportUnits;
+    FLastXPos: TFPReportUnits;
+    FPageFooterYPos: TFPReportUnits;
+    FOverflowed: boolean;
+    FLastGroupCondition: string;
+    FFoundDataBand: boolean;
+    FHasGroupBand: boolean;
+    FHasGroupFooter: boolean;
+    FHasReportSummaryBand: boolean;
+    FDataHeaderPrinted: boolean;
+    FCurrentColumn: UInt8;
+    FMultiColumn: boolean;
+    FHeaderList: TBandList;
+    FFooterList: TBandList;
+    FRTPage: TFPReportCustomPage;
+    FRTBand: TFPReportCustomBand;
+    FCurrentRTColumnFooterBand: TFPReportCustomColumnFooterBand;
+    FDataLevelStack: UInt8;
+    procedure ClearBandList;
+    function GetPage(AIndex: integer): TFPReportCustomPage;
+    function GetPerDesignerPageCount(Index : Cardinal): Cardinal;
+    function GetRTCurPageIdx: Integer;
+    function GetRTObjects: TFPList;
+    procedure SetGetPerDesignerPageCount(Index : Cardinal; AValue: Cardinal);
+    Function GetPageNumberPerDesignerPage : Integer;
+    Procedure SetPageNumberPerDesignerPage(aValue : Integer);
+  protected
+    function HandleBandVisibility(aBand: TFPReportCustomBand; doRecalcLayout : Boolean): Boolean;
+    procedure CheckNewOrOverFlow(CheckMulticolumn: Boolean = True);
+    procedure CheckRemaining(CheckMulticolumn: Boolean = True);
+    procedure SetPageCount(aCount : Integer);
+    procedure IncPageNumberPerDesignerPage;
+    procedure InitRTCurPageIdx;
+    procedure IncPageNumber;
+    Procedure InitPageNumber;
+    function FRTCurBand : TFPReportCustomBand;
+    Function IsFirstPass : Boolean;
+    procedure InitPass(aPassIdx: Integer); virtual;
+    procedure InitBandList(aPage: TFPReportCustomPage; aDataLoop: TFPReportData); virtual;
+    procedure InitDesignPage(aPageIdx: integer); virtual;
+    procedure RunDataLoop(lPageIdx: Integer; lPageData: TFPReportData); virtual;
+    procedure RecalcBandLayout(ABand: TFPReportCustomBand); virtual;
+    procedure PopulateFooterList(APage: TFPReportCustomPage); virtual;
+    procedure PopulateHeaderList(APage: TFPReportCustomPage);virtual;
+    procedure RemoveTitleBandFromHeaderList;virtual;
+    procedure RemoveColumnFooterFromFooterList;virtual;
+    procedure UpdateSpaceRemaining(const ABand: TFPReportCustomBand; const AUpdateYPos: boolean = True);virtual;
+    procedure CommonRuntimeBandProcessing(const aBand: TFPReportCustomBand); virtual;
+    function MaybeSkipBand(const ADsgnBand: TFPReportCustomBand): boolean; virtual;
+    procedure ShowDataBand(const aBand: TFPReportCustomDataBand);virtual;
+    procedure ShowDataFooterBand(const aBand: TFPReportCustomDataFooterBand); virtual;
+    procedure ShowDataHeaderBand(const aBand: TFPReportCustomDataHeaderBand); virtual;
+    procedure ShowDetailBand(const AMasterBand: TFPReportCustomDataBand);virtual;
+    function ShowPageHeaderBand(const aBand: TFPReportCustomBand): boolean; virtual;
+    function ShowColumnHeaderBand(const aBand: TFPReportCustomBand): boolean; virtual;
+    procedure ShowFooterBand(aBand: TFPReportCustomPageFooterBand); virtual;
+    procedure ShowColumnFooterBand(APage: TFPReportCustomPage; ABand: TFPReportCustomColumnFooterBand); virtual;
+    Procedure HandleHeaderBands; virtual;
+    Procedure HandleFooterBands; virtual;
+    procedure HandleDatabands; virtual;
+    procedure HandleGroupBands; virtual;
+    procedure HandleGroupFooters; virtual;
+    procedure HandleReportSummaries; virtual;
+    procedure PrepareGroupHeaderBand(aBand: TFPReportCustomGroupHeaderBand); virtual;
+    function NoSpaceRemaining: boolean;virtual;
+    procedure StartNewPage; virtual;
+    procedure StartNewColumn;virtual;
+    procedure HandleOverflowed;virtual;
+    Procedure DoExecute; virtual;
+    // In case descendents need these, make them available
+    Property PerDesignerPageCount [Index : Cardinal] : Cardinal Read GetPerDesignerPageCount Write SetGetPerDesignerPageCount;
+    property Pages[AIndex: integer]: TFPReportCustomPage read GetPage;
+    property RTObjects: TFPList read GetRTObjects;
+    Property RTCurPageIdx : Integer Read GetRTCurPageIdx;
+    Property RTCurColumn : UInt8 Read FCurrentColumn;
+    Property RTCurPage : TFPReportCustomPage Read FRTPage;
+    Property RTCurBand : TFPReportCustomBand Read FRTBand;
+    Property RTCurColumnFooterBand : TFPReportCustomColumnFooterBand Read FCurrentRTColumnFooterBand;
+    Property PageIdx : Integer Read FPageIdx;
+    Property PageNumberPerDesignerPage : Integer Read GetPageNumberPerDesignerPage Write SetPageNumberPerDesignerPage;
+  Public
+    Procedure Execute(aReport : TFPCustomReport);
+    Property Report : TFPCustomReport Read FMyReport;
+  end;
+
   EReportError = class(Exception);
   EReportError = class(Exception);
   EReportExportError = class(EReportError);
   EReportExportError = class(EReportError);
   EReportFontNotFound = class(EReportError);
   EReportFontNotFound = class(EReportError);
@@ -6479,6 +6577,7 @@ begin
 end;
 end;
 
 
 procedure TFPCustomReport.BuiltinGetPageCount(var Result: TFPExpressionResult; const Args: TExprParameterArray);
 procedure TFPCustomReport.BuiltinGetPageCount(var Result: TFPExpressionResult; const Args: TExprParameterArray);
+
 begin
 begin
   if UsePageCountMarker then
   if UsePageCountMarker then
     Result.ResString := cPageCountMarker
     Result.ResString := cPageCountMarker
@@ -6486,21 +6585,6 @@ begin
     Result.ResString := IntToStr(FPageCount);
     Result.ResString := IntToStr(FPageCount);
 end;
 end;
 
 
-procedure TFPCustomReport.RecalcBandLayout(ABand: TFPReportCustomBand);
-var
-  i: integer;
-  e: TFPReportElement;
-begin
-  for i := ABand.ChildCount-1 downto 0 do
-  begin
-    e := ABand.Child[i];
-    if not e.EvaluateVisibility then
-    begin
-      ABand.RemoveChild(e);
-      FreeAndNil(e);
-    end;
-  end;
-end;
 
 
 procedure TFPCustomReport.EmptyRTObjects;
 procedure TFPCustomReport.EmptyRTObjects;
 begin
 begin
@@ -6638,6 +6722,7 @@ begin
 end;
 end;
 
 
 procedure TFPCustomReport.DoGetExpressionVariableValue(var Result: TFPExpressionResult; constref AName: ShortString);
 procedure TFPCustomReport.DoGetExpressionVariableValue(var Result: TFPExpressionResult; constref AName: ShortString);
+
 var
 var
   p: integer;
   p: integer;
   b: integer;
   b: integer;
@@ -6719,3160 +6804,3209 @@ end;
 
 
 procedure TFPCustomReport.DoPrepareReport;
 procedure TFPCustomReport.DoPrepareReport;
 
 
-var
-  lPageIdx: integer;
-  b: integer;
-  s: string;
-  lNewPage: boolean;  // indicates if a new ReportPage needs to be created - used if DataBand spans multiple pages for example
-  lNewColumn: boolean;
-  lNewGroupHeader: boolean;
-  lDsgnBand: TFPReportCustomBand;
-  lLastDsgnDataBand: TFPReportCustomDataBand;
-  lRTPage: TFPReportCustomPage;
-  lRTBand: TFPReportCustomBand;
-  lSpaceLeft: TFPReportUnits;
-  lLastYPos: TFPReportUnits;
-  lLastXPos: TFPReportUnits;
-  lPageFooterYPos: TFPReportUnits;
-  lOverflowed: boolean;
-  lLastGroupCondition: string;
-  lFoundDataBand: boolean;
-  lHasGroupBand: boolean;
-  lHasGroupFooter: boolean;
-  lHasReportSummaryBand: boolean;
-  lDataHeaderPrinted: boolean;
-  lCurrentColumn: UInt8;
-  lMultiColumn: boolean;
-  lHeaderList: TBandList;
-  lFooterList: TBandList;
-  lNewRTColumnFooterBand: TFPReportCustomColumnFooterBand;
-  lDataLevelStack: UInt8;
-  lPassCount: UInt8;
-  lPassIdx: UInt8;
-
-  procedure RemoveTitleBandFromHeaderList;
-  var
-    idx: integer;
-    lBand: TFPReportCustomBand;
-  begin
-    idx := lHeaderList.Find(TFPReportCustomTitleBand, lBand);
-    if idx > -1 then
-      lHeaderList.Delete(idx);
-  end;
+Var
+  L : TFPReportLayouter;
 
 
-  procedure RemoveColumnFooterFromFooterList;
-  var
-    idx: integer;
-    lBand: TFPReportCustomBand;
-  begin
-    idx := lFooterList.Find(TFPReportCustomColumnFooterBand, lBand);
-    if idx > -1 then
-      lFooterList.Delete(idx);
+begin
+  FPageCount:=0;
+  FBands:=Nil;
+  L:=CreateLayouter;
+  try
+    FBands:=TBandList.Create;
+    SetLength(FPerDesignerPageCount, PageCount);
+    L.Execute(Self);
+  finally
+    SetLength(FPerDesignerPageCount, 0);
+    Fbands.Free;
+    L.Free;
   end;
   end;
+end;
 
 
-  procedure UpdateSpaceRemaining(const ABand: TFPReportCustomBand; const AUpdateYPos: boolean = True);
-  begin
-    lSpaceLeft := lSpaceLeft - ABand.RTLayout.Height;
-    if AUpdateYPos then
-      lLastYPos := lLastYPos + ABand.RTLayout.Height;
-  end;
+procedure TFPCustomReport.DoBeginReport;
+begin
+  if Assigned(FOnBeginReport) then
+    FOnBeginReport;
+end;
 
 
-  procedure CommonRuntimeBandProcessing(const ADsgnBand: TFPReportCustomBand);
-  begin
-    ADsgnBand.PrepareObjects;
-    lRTBand := FRTCurBand;
-    lRTBand.RecalcLayout;
-    lRTBand.BeforePrint;
-    lRTBand.RTLayout.Top := lLastYPos;
-    lRTBand.RTLayout.Left := lLastXPos;
-  end;
+procedure TFPCustomReport.DoEndReport;
+begin
+  if Assigned(FOnEndReport) then
+    FOnEndReport;
+end;
 
 
-  { Result of True means ADsgnBand must be skipped. Result of False means ADsgnBand
-    must be rendered (ie: not skipped).  }
-  function DoVisibleOnPageChecks(const ADsgnBand: TFPReportCustomBand): boolean;
-  begin
-    Result := True;
-    if ADsgnBand.VisibleOnPage = vpAll then
-    begin
-      // do nothing special
-    end
-    else if (FPageNumberPerDesignerPage = 1) then
-    begin // first page rules
-      if (ADsgnBand.VisibleOnPage in [vpFirstOnly, vpFirstAndLastOnly]) then
-      begin
-        // do nothing special
-      end
-      else if (ADsgnBand.VisibleOnPage in [vpNotOnFirst, vpLastOnly, vpNotOnFirstAndLast]) then
-        Exit; // user asked to skip this band
-    end
-    else if (FPageNumberPerDesignerPage > 1) then
-    begin  // multi-page rules
-      if ADsgnBand.VisibleOnPage in [vpFirstOnly] then
-        Exit  // user asked to skip this band
-      else if ADsgnBand.VisibleOnPage in [vpNotOnFirst] then
-      begin
-        // do nothing special
-      end
-      else if (not IsFirstPass) then
-      begin // last page rules
-        if (ADsgnBand.VisibleOnPage in [vpLastOnly, vpFirstAndLastOnly]) and (FPageNumberPerDesignerPage < FPerDesignerPageCount[lPageIdx]) then
-          Exit
-        else if (ADsgnBand.VisibleOnPage in [vpNotOnLast, vpFirstOnly, vpNotOnFirstAndLast]) and (FPageNumberPerDesignerPage = FPerDesignerPageCount[lPageIdx]) then
-          Exit; // user asked to skip this band
-      end;
-    end;
-    Result := False;
-  end;
+procedure TFPCustomReport.RestoreDefaultVariables;
 
 
-  { Result of True means ADsgnBand must be skipped. Result of False means ADsgnBand
-    must be rendered (ie: not skipped).  }
-  function ShowPageHeaderBand(const ADsgnBand: TFPReportCustomBand): boolean;
-  begin
-    Result := True;
-    if not (ADsgnBand is TFPReportCustomPageHeaderBand) then
-      Exit;
+Var
+  I : Integer;
 
 
-    if DoVisibleOnPageChecks(ADsgnBand as TFPReportCustomPageHeaderBand) then
-      Exit;
+begin
+  For I:=0 to FVariables.Count-1 do
+    FVariables[i].RestoreValue;
+end;
 
 
-    CommonRuntimeBandProcessing(ADsgnBand);
-    Result := False;
-  end;
+procedure TFPCustomReport.InitializeDefaultExpressions;
 
 
-  function ShowColumnHeaderBand(const ADsgnBand: TFPReportCustomBand): boolean;
-  var
-    lBand: TFPReportCustomBand;
-  begin
-    Result := False;
-    CommonRuntimeBandProcessing(ADsgnBand);
-    { Only once we show the first column header do we take into account
-      the column footer. }
-    lBand := Pages[lPageIdx].FindBand(TFPReportCustomColumnFooterBand);
-    lFooterList.Add(lBand);
-    if Assigned(lBand) then
-      lSpaceLeft := lSpaceLeft - lBand.Layout.Height;
-  end;
+Var
+  I : Integer;
+  V : TFPReportVariable;
+  lHasAggregates: Boolean;
 
 
-  function LayoutColumnFooterBand(APage: TFPReportCustomPage; ABand: TFPReportCustomColumnFooterBand): TFPReportCustomColumnFooterBand;
-  var
-    lBandCount: integer;
-    lOverflowBand: TFPReportCustomBand;
+begin
+  FExpr.Clear;
+  FExpr.Identifiers.Clear;
+  FExpr.BuiltIns := [bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser, bcAggregate];
+  FExpr.Identifiers.AddDateTimeVariable('TODAY', Date);
+  FExpr.Identifiers.AddStringVariable('AUTHOR', Author);
+  FExpr.Identifiers.AddStringVariable('TITLE', Title);
+  FExpr.Identifiers.AddFunction('RecNo', 'I', '', @BuiltinExprRecNo);
+  FExpr.Identifiers.AddFunction('PageNo', 'I', '', @BuiltinGetPageNumber);
+  FExpr.Identifiers.AddFunction('PageNoPerDesignerPage', 'I', '', @BuiltInGetPageNoPerDesignerPage);
+  lHasAggregates:=false;
+  For I:=0 to FVariables.Count-1 do
   begin
   begin
-    lBandCount := lRTPage.BandCount - 1;
-    lOverflowBand := lRTPage.Bands[lBandCount]; { store reference to band that caused the new column }
+    V:=FVariables[i];
+    V.SaveValue;
+    if V.Expression = '' then
+      FExpr.Identifiers.AddVariable(V.Name,V.DataType,@V.GetRTValue)
+    else
+      lHasAggregates:=true;
+  end;
+  if lHasAggregates then
+    TwoPass:=true;
+  if UsePageCountMarker then
+    FExpr.Identifiers.AddFunction('PageCount', 'S', '', @BuiltinGetPageCount)
+  else
+    FExpr.Identifiers.AddFunction('PageCount', 'I', '', @BuiltinGetPageCount);
+end;
 
 
-    CommonRuntimeBandProcessing(ABand);
-    Result := TFPReportCustomColumnFooterBand(lRTBand);
+procedure TFPCustomReport.InitializeExpressionVariables(const APage: TFPReportCustomPage; const AData: TFPReportData);
+var
+  i: Integer;
+  f: string;
+  r: TResultType;
+  d: string;
+  v: TFPReportVariable;
 
 
-    if lPageFooterYPos = -1 then
-      lRTBand.RTLayout.Top := (APage.RTLayout.Top + APage.RTLayout.Height) - lRTBand.RTLayout.Height
-    else
-      lRTBand.RTLayout.Top := lPageFooterYPos - lRTBand.RTLayout.Height;
-    if Result.FooterPosition = fpAfterLast then
-    begin
-      if lNewColumn or lOverflowed then
-        { take height of overflowed band into account }
-        lRTBand.RTLayout.Top := lLastYPos - lOverflowBand.RTLayout.Height
+  function ReportKindToResultType(const AType: TFPReportFieldKind): TResultType;
+  begin
+    case AType of
+      rfkString:      Result := rtString;
+      rfkBoolean:     Result := rtBoolean;
+      rfkInteger:     Result := rtInteger;
+      rfkFloat:       Result := rtFloat;
+      rfkDateTime:    Result := rtDateTime;
+      rfkStream:      Result := rtString; //  TODO:  What do we do here?????
       else
       else
-        lRTBand.RTLayout.Top := lLastYPos;
+        Result := rtString;
     end;
     end;
   end;
   end;
 
 
-  function NoSpaceRemaining: boolean;
+
+begin
+  {$ifdef gdebug}
+  writeln('********** TFPCustomReport.InitializeExpressionVariables');
+  {$endif}
+  F:='';
+  For I:=0 to FExpr.Identifiers.Count-1 do
+    f:=f+FExpr.Identifiers[i].Name+'; ';
+  for i := 0 to AData.DataFields.Count-1 do
   begin
   begin
-    if lSpaceLeft <= 0 then
-    begin
-      Result := True;
-      if lMultiColumn and (lCurrentColumn < Pages[lPageIdx].ColumnCount) then
+    d := AData.Name;
+    f := AData.DataFields[i].FieldName;
+    r := ReportKindToResultType(AData.DataFields[i].FieldKind);
+    if d <> '' then
       begin
       begin
-        lNewColumn := True;
+      {$ifdef gdebug}
+      writeln('registering (dotted name)... '+ d+'.'+f);
+      {$endif}
+      FExpr.Identifiers.AddVariable(d+'.'+f, r, @DoGetExpressionVariableValue);
       end
       end
-      else
+    else
       begin
       begin
-        lOverflowed := True;
-        lNewPage := True;
+      {$ifdef gdebug}
+      writeln('registering... '+ f);
+      {$endif}
+      FExpr.Identifiers.AddVariable(f, r, @DoGetExpressionVariableValue);
       end;
       end;
-    end
-    else
-      Result := False;
   end;
   end;
-
-  procedure StartNewColumn;
-  var
-    lIdx: integer;
-    lBandCount: integer;
-    lBand: TFPReportCustomBand;
-    lOverflowBand: TFPReportCustomBand;
+  if APage.Data = AData then
   begin
   begin
-    if Assigned(lLastDsgnDataBand) then
-      ClearDataBandLastTextValues(lLastDsgnDataBand);
-
-    if lMultiColumn and (lFooterList.Find(TFPReportCustomColumnFooterBand) <> nil) then
-      lBandCount := lRTPage.BandCount - 2  // skip over the ColumnFooter band
-    else
-      lBandCount := lRTPage.BandCount - 1;
-    lOverflowBand := lRTPage.Bands[lBandCount]; { store reference to band that caused the new column }
-    lSpaceLeft := Pages[lPageIdx].Layout.Height; // original designer page
-    lRTPage := TFPReportCustomPage(RTObjects[FRTCurPageIdx]);
-
-    lLastYPos := lRTPage.RTLayout.Top;
-    lLastXPos := lLastXPos + lOverflowBand.RTLayout.Width + Pages[lPageIdx].ColumnGap;
-
-    { Adjust starting Y-Pos based on bands in lHeaderList. }
-    for lIdx := 0 to lHeaderList.Count-1 do
+    For I:=0 to FVariables.Count-1 do
     begin
     begin
-      lBand := lHeaderList[lIdx];
-
-      if lBand is TFPReportCustomPageHeaderBand then
+      v:=FVariables[I];
+      if v.Expression<>'' then
       begin
       begin
-        if DoVisibleOnPageChecks(lBand) then
-          Continue;
-      end
-      else if lBand is TFPReportCustomColumnHeaderBand then
+        FExpr.Expression:=v.Expression;
+        FExpr.ExtractNode(v.FExpressionNode);
+        v.FIsAggregate:=v.FExpressionNode.IsAggregate;
+        if v.FExpressionNode.HasAggregate and
+        not v.FExpressionNode.IsAggregate then
+          raise EReportError.CreateFmt(SErrExprVarisbleAggregateOnWrongLevel, [v.FExpressionNode.AsString]);
+        if not v.FIsAggregate then begin
+          v.FResetType:=rtNone;
+          v.FResetValueExpression:='';
+        end;
+      end;
+      if v.ResetValueExpression<>'' then
       begin
       begin
-        if ShowColumnHeaderBand(lBand) then
-          Continue;
+        FExpr.Expression := v.ResetValueExpression;
+        FExpr.ExtractNode(v.FResetValueExpressionNode);
       end;
       end;
-
-      lSpaceLeft := lSpaceLeft - lBand.Layout.Height;
-      lLastYPos := lLastYPos + lBand.Layout.Height;
     end;
     end;
+    For I:=0 to FVariables.Count-1 do
+    begin
+      v:=FVariables[I];
+      if v.Expression<>'' then
+        FExpr.Identifiers.AddVariable(v.Name, v.DataType, @v.GetRTExpressionValue);
+    end;
+  end;
+end;
 
 
-    inc(lCurrentColumn);
+procedure TFPCustomReport.CacheMemoExpressions(const APageIdx: integer; const AData: TFPReportData);
+var
+  b: integer;
+  c: integer;
+  m: TFPReportCustomMemo;
+begin
+  for b := 0 to Pages[APageIdx].BandCount-1 do
+  begin
+    if Pages[APageIdx].Bands[b] is TFPReportCustomBandWithData then
+    begin
+      if TFPReportCustomBandWithData(Pages[APageIdx].Bands[b]).Data <> AData then
+        Continue;  // band is from a different data-loop
+    end;
 
 
-    { If footer band exists, reduce available space }
-    lBand := lRTPage.FindBand(TFPReportCustomPageFooterBand);
-    if Assigned(lBand) then
-      UpdateSpaceRemaining(lBand, False);
+    for c := 0 to Pages[APageIdx].Bands[b].ChildCount-1 do
+      if Pages[APageIdx].Bands[b].Child[c] is TFPReportCustomMemo then
+      begin
+        m := TFPReportCustomMemo(Pages[APageIdx].Bands[b].Child[c]);
+        m.ParseText;
+      end;
+  end; { bands }
+end;
 
 
-    if NoSpaceRemaining then
-      Exit;
+constructor TFPCustomReport.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FReportData:=CreateReportData;
+  FRTObjects := TFPList.Create;
+  FImages := CreateImages;
+  FVariables:=CreateVariables;
+  FRTCurPageIdx := -1;
+  FDateCreated := Now;
+  FTwoPass := False;
+  FIsFirstPass := False;
+end;
 
 
-    { Fix position of last band that caused the new column }
-    lOverflowBand.RTLayout.Left := lLastXPos;
-    lOverflowBand.RTLayout.Top := lLastYPos;
-    { Adjust the next starting point of the next data band. }
-    UpdateSpaceRemaining(lOverflowBand);
+function TFPCustomReport.CreateImages: TFPReportImages;
 
 
-    lNewColumn := False;
-  end;
+begin
+  Result:=TFPReportImages.Create(self, TFPReportImageItem);
+end;
 
 
-  procedure HandleOverflowed;
-  var
-    lPrevRTPage: TFPReportCustomPage;
-    lOverflowBand: TFPReportCustomBand;
-    lBandCount: integer;
-  begin
-    lOverflowed := False;
-    lPrevRTPage := TFPReportCustomPage(RTObjects[FRTCurPageIdx-1]);
-    if lMultiColumn and (lFooterList.Find(TFPReportCustomColumnFooterBand) <> nil) then
-      lBandCount := lPrevRTPage.BandCount - 2  // skip over the ColumnFooter band
-    else
-      lBandCount := lPrevRTPage.BandCount - 1;
-    lOverflowBand := lPrevRTPage.Bands[lBandCount]; // get the last band - the one that didn't fit
-    lPrevRTPage.RemoveChild(lOverflowBand);
-    lRTPage.AddChild(lOverflowBand);
+function TFPCustomReport.CreateVariables: TFPReportVariables;
 
 
-    { Fix position of last band that caused the overflow }
-    lOverflowBand.RTLayout.Top := lLastYPos;
-    lOverflowBand.RTLayout.Left := lLastXPos;
-    UpdateSpaceRemaining(lOverflowBand);
-  end;
+begin
+  Result:=TFPReportVariables.Create(Self,TFPReportVariable);
+end;
 
 
-  procedure LayoutFooterBand;
-  var
-    lBand: TFPReportCustomPageFooterBand;
-  begin
-    lPageFooterYPos := -1;
-    if not (lDsgnBand is TFPReportCustomPageFooterBand) then
-      Exit;
+function TFPCustomReport.CreateReportData : TFPReportDataCollection;
 
 
-    lBand := TFPReportCustomPageFooterBand(lDsgnBand);
-    if DoVisibleOnPageChecks(lBand) then
-      Exit;
+begin
+  Result:=TFPReportDataCollection.Create(TFPReportDataItem);
+end;
 
 
-    lDsgnBand.PrepareObjects;
-    lRTBand := FRTCurBand;
-    lRTBand.RecalcLayout;
-    lRTBand.BeforePrint;
-    lRTBand.RTLayout.Top := (lRTPage.RTLayout.Top + lRTPage.RTLayout.Height) - lRTBand.RTLayout.Height;
-    lPageFooterYPos := lRTBand.RTLayout.Top;
-    // We don't adjust lLastYPos because this is a page footer
-    UpdateSpaceRemaining(lRTBand, False);
-  end;
+function TFPCustomReport.CreateLayouter: TFPReportLayouter;
+begin
+  Result:=TFPReportLayouter.Create(Self);
+end;
 
 
-  procedure PopulateHeaderList(APage: TFPReportCustomPage);
-  begin
-    lHeaderList.Clear;
-    lHeaderList.Add(APage.FindBand(TFPReportCustomPageHeaderBand));
-    lHeaderList.Add(APage.FindBand(TFPReportCustomTitleBand));
-    if lMultiColumn then
-      lHeaderList.Add(Pages[lPageIdx].FindBand(TFPReportColumnHeaderBand));
-  end;
+destructor TFPCustomReport.Destroy;
+begin
+  EmptyRTObjects;
+  FreeAndNil(FReportData);
+  FreeAndNil(FRTObjects);
+  FreeAndNil(FPages);
+  FreeAndNil(FExpr);
+  FreeAndNil(FReferenceList);
+  FreeAndNil(FImages);
+  FreeAndNil(FVariables);
+  inherited Destroy;
+end;
 
 
-  procedure PopulateFooterList(APage: TFPReportCustomPage);
-  begin
-    lFooterList.Clear;
-    lFooterList.Add(APage.FindBand(TFPReportCustomPageFooterBand));
-  end;
+procedure TFPCustomReport.SaveDataToNames;
 
 
-  procedure StartNewPage;
-  var
-    lIdx: integer;
-  begin
-    if Assigned(lLastDsgnDataBand) then
-      ClearDataBandLastTextValues(lLastDsgnDataBand);
-    lSpaceLeft := Pages[lPageIdx].Layout.Height; // original designer page
-    Pages[lPageIdx].PrepareObjects;  // creates a new page object
-    //not needed here, it's done in TFPReportCustomPage.PrepareObjects: FRTCurPageIdx := RTObjects.Count-1;
-    lRTPage := TFPReportCustomPage(RTObjects[FRTCurPageIdx]);
-    lLastYPos := lRTPage.RTLayout.Top;
-    lLastXPos := lRTPage.RTLayout.Left;
-    Inc(FPageNumber);
-    lCurrentColumn := 1;
-    lNewColumn := False;
-
-    if IsFirstPass then
-      FPerDesignerPageCount[lPageIdx] := FPerDesignerPageCount[lPageIdx] + 1;
-
-    if (FPageNumberPerDesignerPage = 1) then
-      RemoveTitleBandFromHeaderList;
-    inc(FPageNumberPerDesignerPage);
-
-    { Show all header bands }
-    for lIdx := 0 to lHeaderList.Count - 1 do
-    begin
-      lDsgnBand := lHeaderList[lIdx];
-      if lDsgnBand is TFPReportCustomPageHeaderBand then
-      begin
-        if ShowPageHeaderBand(lDsgnBand) then
-          Continue;
-      end
-      else if lDsgnBand is TFPReportCustomColumnHeaderBand then
-      begin
-        if ShowColumnHeaderBand(lDsgnBand) then
-          Continue;
-      end
-      else
-        CommonRuntimeBandProcessing(lDsgnBand);
-      UpdateSpaceRemaining(lRTBand);
-    end;
+Var
+  I : Integer;
 
 
-    { Show footer band if it exists }
-    lDsgnBand := lFooterList.Find(TFPReportCustomPageFooterBand);
-    if Assigned(lDsgnBand) then
-      LayoutFooterBand;
+begin
+  For I:=0 to PageCount-1 do
+    Pages[i].SaveDataToNames;
+end;
 
 
-    lNewPage := False;
-  end;
+procedure TFPCustomReport.RestoreDataFromNames;
+Var
+  I : Integer;
 
 
-  procedure ShowDataBand(const ADsgnBand: TFPReportCustomDataBand);
-  var
-    lDsgnChildBand: TFPReportChildBand;
-    lLastDataBand: TFPReportCustomBand;
-  begin
-    lLastDsgnDataBand := ADsgnBand;
-    CommonRuntimeBandProcessing(ADsgnBand);
-    if lRTBand.EvaluateVisibility then
-    begin
-      lLastDataBand := lRTBand;
-      RecalcBandLayout(lRTBand);
-      UpdateSpaceRemaining(lRTBand);
-      if NoSpaceRemaining then
-      begin
-        { Process ColumnFooterBand as needed }
-        if lMultiColumn then
-        begin
-          lNewRTColumnFooterBand := TFPReportCustomColumnFooterBand(lFooterList.Find(TFPReportCustomColumnFooterBand));
-          if Assigned(lNewRTColumnFooterBand) then
-            LayoutColumnFooterBand(lRTPage, lNewRTColumnFooterBand);
-        end;
+begin
+  For I:=0 to PageCount-1 do
+    Pages[i].RestoreDataFromNames;
+end;
 
 
-        if lNewColumn then
-          StartNewColumn;
+procedure TFPCustomReport.AddPage(APage: TFPReportCustomPage);
+begin
+  if not Assigned(FPages) then
+  begin
+    FPages := TFPList.Create;
+    FPages.Add(APage);
+  end
+  else if FPages.IndexOf(APage) = -1 then
+    FPages.Add(APage);
+end;
 
 
-        if lNewPage then
-          StartNewPage;
+procedure TFPCustomReport.RemovePage(APage: TFPReportCustomPage);
+begin
+  if Assigned(FPages) then
+    FPages.Remove(APage);
+end;
 
 
-        { handle overflowed bands. Remove from old page, add to new page }
-        if lOverflowed then
-          HandleOverflowed;
-      end;
-      { process any child bands off of DataBand }
-      lDsgnChildBand := lLastDataBand.ChildBand;
-      while lDsgnChildBand <> nil do
+procedure TFPCustomReport.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+var
+  i: integer;
+begin
+  // ignore AOriginal here as we don't support whole report diffs, only element diffs
+  AWriter.PushElement('Report');
+  try
+    inherited WriteElement(AWriter, AOriginal);
+    // local properties
+    AWriter.WriteString('Title', Title);
+    AWriter.WriteString('Author', Author);
+    AWriter.WriteDateTime('DateCreated', DateCreated);
+    // now the design-time images
+    AWriter.PushElement('Images');
+    try
+      for i := 0 to Images.Count-1 do
       begin
       begin
-        CommonRuntimeBandProcessing(lDsgnChildBand);
-        if lRTBand.EvaluateVisibility then
-        begin
-          RecalcBandLayout(lRTBand);
-          UpdateSpaceRemaining(lRTBand);
-          if NoSpaceRemaining then
-          begin
-            { Process ColumnFooterBand as needed }
-            if lMultiColumn then
-            begin
-              lNewRTColumnFooterBand := TFPReportCustomColumnFooterBand(lFooterList.Find(TFPReportCustomColumnFooterBand));
-              if Assigned(lNewRTColumnFooterBand) then
-                LayoutColumnFooterBand(lRTPage, lNewRTColumnFooterBand);
-            end;
-
-            if lNewColumn then
-              StartNewColumn;
-
-            if lNewPage then
-              StartNewPage;
-
-            { handle overflowed bands. Remove from old page, add to new page }
-            if lOverflowed then
-              HandleOverflowed;
-          end;
-        end
-        else
-        begin
-          // remove invisible band
-          lRTPage.RemoveChild(lRTBand);
-          FreeAndNil(lRTBand);
+        AWriter.PushElement(IntToStr(i)); // use image index as identifier
+        try
+          Images[i].WriteElement(AWriter);
+        finally
+          AWriter.PopElement;
         end;
         end;
-        lDsgnChildBand := lDsgnChildBand.ChildBand;
-      end;  { while ChildBand <> nil }
-    end
-    else
-    begin
-      // remove invisible band
-      lRTPage.RemoveChild(lRTBand);
-      FreeAndNil(lRTBand);
+      end;
+    finally
+      AWriter.PopElement;
     end;
     end;
-  end;
-
-  procedure ShowDataHeaderBand(const ADsgnBand: TFPReportCustomDataHeaderBand);
-  begin
-    if lDataHeaderPrinted then
-      Exit; // nothing further to do
-
-    CommonRuntimeBandProcessing(ADsgnBand);
-    if lRTBand.EvaluateVisibility then
-    begin
-      lDataHeaderPrinted := True;
-      UpdateSpaceRemaining(lRTBand);
-      if NoSpaceRemaining then
+    // now the pages
+    AWriter.PushElement('Pages');
+    try
+      for i := 0 to PageCount - 1 do
       begin
       begin
-        { Process ColumnFooterBand as needed }
-        if lMultiColumn then
-        begin
-          lNewRTColumnFooterBand := TFPReportCustomColumnFooterBand(lFooterList.Find(TFPReportCustomColumnFooterBand));
-          if Assigned(lNewRTColumnFooterBand) then
-            LayoutColumnFooterBand(lRTPage, lNewRTColumnFooterBand);
+        AWriter.PushElement(IntToStr(i)); // use page index as identifier
+        try
+          Pages[i].WriteElement(AWriter);
+        finally
+          AWriter.PopElement;
         end;
         end;
-
-        if lNewColumn then
-          StartNewColumn;
-
-        if lNewPage then
-          StartNewPage;
-
-        { handle overflowed bands. Remove from old page, add to new page }
-        if lOverflowed then
-          HandleOverflowed;
-      end; { no space remaining }
-    end
-    else
-    begin
-      // remove invisible band
-      lRTPage.RemoveChild(lRTBand);
-      FreeAndNil(lRTBand);
-    end;
-  end;
-
-  procedure ShowDataFooterBand(const ADsgnBand: TFPReportCustomDataFooterBand);
-  begin
-    CommonRuntimeBandProcessing(ADsgnBand);
-    if lRTBand.EvaluateVisibility then
-    begin
-      UpdateSpaceRemaining(lRTBand);
-      if NoSpaceRemaining then
-      begin
-        if lNewPage then
-          StartNewPage;
-
-        { handle overflowed bands. Remove from old page, add to new page }
-        if lOverflowed then
-          HandleOverflowed;
-      end; { no space remaining }
-    end
-    else
-    begin
-      // remove invisible band
-      lRTPage.RemoveChild(lRTBand);
-      FreeAndNil(lRTBand);
+      end;
+    finally
+      AWriter.PopElement;
     end;
     end;
+  finally
+    AWriter.PopElement;
   end;
   end;
+  // TODO: Implement writing OnRenderReport, OnBeginReport, OnEndReport
+end;
 
 
-  procedure ShowDetailBand(const AMasterBand: TFPReportCustomDataBand);
-  var
-    lDsgnDetailBand: TFPReportCustomDataBand;
-    lDetailBandList: TBandList;
-    lDetailBand: TFPReportCustomBand;
-    lData: TFPReportData;
-    i: integer;
+procedure TFPCustomReport.ReadElement(AReader: TFPReportStreamer);
+var
+  E: TObject;
+  i: integer;
+  p: TFPReportPage;
+  lImgItem: TFPReportImageItem;
+begin
+  ClearReferenceList;
+  E := AReader.FindChild('Report');
+  if Assigned(E) then
   begin
   begin
-    if AMasterBand = nil then
-      Exit;
-    lDsgnDetailBand := nil;
-    lDetailBandList := TBandList.Create;
+    AReader.PushElement(E);
     try
     try
-      { collect bands of interest }
-      for i := 0 to Pages[lPageIdx].BandCount-1 do
-      begin
-        lDetailBand := Pages[lPageIdx].Bands[i];
-        if (lDetailBand is TFPReportCustomDataBand)
-          and (TFPReportCustomDataBand(lDetailBand).MasterBand = AMasterBand)
-          and (TFPReportCustomDataBand(lDetailBand).Data <> nil) then
-            lDetailBandList.Add(lDetailBand);
-      end;
-      if lDetailBandList.Count = 0 then
-        exit;  // nothing further to do
-      lDetailBandList.Sort(@SortDataBands);
+      inherited ReadElement(AReader);
+      FTitle := AReader.ReadString('Title', Title);
+      FAuthor := AReader.ReadString('Author', Author);
+      FDateCreated := AReader.ReadDateTime('DateCreated', Now);
 
 
-      { process Detail bands }
-      for i := 0 to lDetailBandList.Count-1 do
+      E := AReader.FindChild('Images');
+      if Assigned(E) then
       begin
       begin
-        lDsgnDetailBand := TFPReportCustomDataBand(lDetailBandList[i]);
-        lData := lDsgnDetailBand.Data;
-        if not lData.IsOpened then
+        AReader.PushElement(E);
+        for i := 0 to AReader.ChildCount-1 do
         begin
         begin
-          lData.Open;
-          InitializeExpressionVariables(Pages[lPageIdx], lData);
-          CacheMemoExpressions(lPageIdx, lData);
-        end;
-        lData.First;
+          E := AReader.GetChild(i);
+          AReader.PushElement(E); // child index is the identifier
+          try
+            lImgItem := Images.AddImageItem;
+            lImgItem.ReadElement(AReader);
+          finally
+            AReader.PopElement;
+          end;
+        end; { for i }
+        AReader.PopElement;
+      end;  { images }
 
 
-        if (not lData.EOF) and (lDsgnDetailBand.HeaderBand <> nil) then
-          ShowDataHeaderBand(lDsgnDetailBand.HeaderBand);
-        while not lData.EOF do
+      E := AReader.FindChild('Pages');
+      if Assigned(E) then
+      begin
+        AReader.PushElement(E);
+        for i := 0 to AReader.ChildCount-1 do
         begin
         begin
-          If TwoPass and IsFirstPass then
-            Variables.BuildAggregates;
-          inc(lDataLevelStack);
-          ShowDataBand(lDsgnDetailBand);
-          ShowDetailBand(lDsgnDetailBand);
-          dec(lDataLevelStack);
-          lData.Next;
-        end;  { while not lData.EOF }
-        lDataHeaderPrinted := False;
-
-        if lNewPage then
-          StartNewPage;
-
-        { handle overflowed bands. Remove from old page, add to new page }
-        if lOverflowed then
-          HandleOverflowed;
-
-        // only print if we actually had data
-        if (lData.RecNo > 1) and (lDsgnDetailBand.FooterBand <> nil) then
-          ShowDataFooterBand(lDsgnDetailBand.FooterBand);
+          E := AReader.GetChild(i);
+          AReader.PushElement(E); // child index is the identifier
+          try
+            p := TFPReportPage.Create(self);
+            p.ReadElement(AReader);
+            AddPage(p);
+          finally
+            AReader.PopElement;
+          end;
+        end;  { for i }
+        AReader.PopElement;
+      end; { pages }
 
 
-        lDsgnDetailBand := nil;
-      end;
+      // TODO: Implement reading OnRenderReport, OnBeginReport, OnEndReport
     finally
     finally
-      lDetailBandList.Free;
+      AReader.PopElement;
     end;
     end;
   end;
   end;
+  FixupReferences;
+end;
 
 
+procedure TFPCustomReport.StartRender;
 begin
 begin
-  EmptyRTObjects;
-  lHeaderList := Nil;
-  lFooterList := Nil;
-  FBands := Nil;
-  try
-    lHeaderList := TBandList.Create;
-    lFooterList := TBandList.Create;
-    FBands := TBandList.Create;
-    SetLength(FPerDesignerPageCount, PageCount);
+  inherited StartRender;
+  DoBeforeRenderReport;
+end;
 
 
-    if TwoPass then
-      lPassCount := 2
-    else
-      lPassCount := 1;
+procedure TFPCustomReport.EndRender;
+begin
+  inherited EndRender;
+  DoAfterRenderReport;
+end;
 
 
-    FPageCount := 0;
-    for lPassIdx := 1 to lPassCount do
+function TFPCustomReport.FindRecursive(const AName: string): TFPReportElement;
+var
+  p, b, c: integer;
+begin
+  Result := nil;
+  if AName = '' then
+    Exit;
+  for p := 0 to PageCount-1 do
+  begin
+    for b := 0 to Pages[p].BandCount-1 do
     begin
     begin
-      IsFirstPass := lPassIdx = 1;
-      lHeaderList.Clear;
-      lFooterList.Clear;
-      FBands.Clear;
-      FRTCurPageIdx := -1;
-      lOverflowed := False;
-      lHasGroupBand := False;
-      lHasGroupFooter := False;
-      lHasReportSummaryBand := False;
-      lDataHeaderPrinted := False;
-      lLastGroupCondition := '';
-      FPageNumber := 0;
-      lDataLevelStack := 0;
-
-      for lPageIdx := 0 to PageCount-1 do
-      begin
-        lNewPage := True;
-        lNewGroupHeader := True;
-        lCurrentColumn := 1;
-        lMultiColumn := Pages[lPageIdx].ColumnCount > 1;
-        lNewColumn := False;
-        lPageFooterYPos := -1;
-        FPageData := Pages[lPageIdx].Data;
-        FPageNumberPerDesignerPage := 0;
-        lFoundDataBand := False;
-        lLastDsgnDataBand := nil;
-
-        if Assigned(FPageData) then
-        begin
-          if not FPageData.IsOpened then
-            FPageData.Open;
-          if IsFirstPass then
-          begin
-            InitializeExpressionVariables(Pages[lPageIdx], FPageData);
-            CacheMemoExpressions(lPageIdx, FPageData);
-          end
-          else
-            Variables.InitSecondPass;
+      if SameText(Pages[p].Bands[b].Name, AName) then
+        Result := Pages[p].Bands[b];
+      if Assigned(Result) then
+        Exit;
 
 
-          FPageData.First;
+      for c := 0 to Pages[p].Bands[b].ChildCount-1 do
+      begin
+        if SameText(Pages[p].Bands[b].Child[c].Name, AName) then
+          Result := Pages[p].Bands[b].Child[c];
+        if Assigned(Result) then
+          Exit;
+      end;
+    end;
+  end;
+end;
 
 
-          // Create a list of band that need to be printed as page headers
-          PopulateHeaderList(Pages[lPageIdx]);
+procedure TFPCustomReport.RunReport;
+begin
+  DoBeginReport;
 
 
-          // Create a list of bands that need to be printed as page footers
-          PopulateFooterList(Pages[lPageIdx]);
+  StartLayout;
+  FExpr := TFPexpressionParser.Create(nil);
+  try
+    InitializeDefaultExpressions;
+    DoPrepareReport;
+  finally
+    RestoreDefaultVariables;
+    FreeAndNil(FExpr);
+  end;
+  EndLayout;
 
 
-          // find Bands of interest
-          FBands.Clear;
-          for b := 0 to Pages[lPageIdx].BandCount-1 do
-          begin
-            lDsgnBand := Pages[lPageIdx].Bands[b];
-            if (lDsgnBand is TFPReportCustomDataBand) then
-            begin
-              if TFPReportCustomDataBand(lDsgnBand).Data = FPageData then
-              begin
-                { Do a quick sanity check - we may not have more than one master data band }
-                if lFoundDataBand then
-                  ReportError(SErrMultipleDataBands);
-                FBands.Add(lDsgnBand);
-                lFoundDataBand := True;
-              end
-              else
-                continue; // it's a databand but not for the current data loop
-            end
-            else
-            begin
-              if (lDsgnBand is TFPReportCustomGroupHeaderBand) and (TFPReportCustomGroupHeaderBand(lDsgnBand).GroupHeader <> nil) then
-                continue; // this is not the toplevel GroupHeader Band.
-              if lDsgnBand is TFPReportCustomGroupFooterBand then
-                continue; // we will get the Footer from the GroupHeaderBand.FooterBand property
-              FBands.Add(Pages[lPageIdx].Bands[b]);  { all non-data bands are of interest }
-            end;
-
-            if lDsgnBand is TFPReportCustomGroupHeaderBand then
-            begin
-              lHasGroupBand := True;
-              if Assigned(TFPReportCustomGroupHeaderBand(lDsgnBand).GroupFooter) then
-                lHasGroupFooter := True;
-            end
-            else if lDsgnBand is TFPReportCustomSummaryBand then
-              lHasReportSummaryBand := True;
-          end;
-
-          while not FPageData.EOF do
-          begin
-            if TwoPass and IsFirstPass then
-              Variables.BuildAggregates;
-            if lNewColumn then
-              StartNewColumn;
-
-            if lNewPage then
-              StartNewPage;
-
-            { handle overflowed bands. Remove from old page, add to new page }
-            if lOverflowed then
-              HandleOverflowed;
-
-            if lHasGroupBand then
-            begin
-              if lLastGroupCondition = '' then
-                lNewGroupHeader := True
-              else
-              begin
-                { Do GroupHeader evaluation }
-                for b := 0 to FBands.Count-1 do
-                begin
-                  lDsgnBand := TFPReportCustomBand(FBands[b]);
-                  { group header }
-                  if lDsgnBand is TFPReportCustomGroupHeaderBand then
-                  begin
-                    // Writeln('Found group with expr: ',TFPReportCustomGroupHeaderBand(lDsgnBand).GroupCondition);
-                    s := TFPReportCustomGroupHeaderBand(lDsgnBand).Evaluate;
-                    // Writeln('Group new ? "',lLastGroupCondition,'" <> "', s,'"');
-                    if (lLastGroupCondition <> s) then
-                    begin
-                      lNewGroupHeader := True;
-                      { process group footer }
-                      if Assigned(TFPReportCustomGroupHeaderBand(lDsgnBand).GroupFooter) then
-                      begin
-                        FRTUseLastValues:=true;
-                        try
-                          lDsgnBand := TFPReportCustomGroupHeaderBand(lDsgnBand).GroupFooter;
-                          CommonRuntimeBandProcessing(lDsgnBand);
-                        finally
-                          FRTUseLastValues:=false;
-                        end;
-                        UpdateSpaceRemaining(lRTBand);
-                        if NoSpaceRemaining then
-						            begin
-                          if lNewColumn then
-                            StartNewColumn;
-
-                          if lNewPage then
-                            StartNewPage;
-
-                          { handle overflowed bands. Remove from old page, add to new page }
-                          if lOverflowed then
-                            HandleOverflowed;
-                        end;
-                      end; { group footer }
-                    end;
-                  end; { group header }
-                end;  { bands for loop }
-              end;  { if/else }
-
-              if lNewGroupHeader then
-              begin
-                for b := 0 to FBands.Count-1 do
-                begin
-                  lDsgnBand := TFPReportCustomBand(FBands[b]);
-                  { group header }
-                  if lDsgnBand is TFPReportCustomGroupHeaderBand then
-                  begin
-                    if Assigned(lLastDsgnDataBand) then
-                      ClearDataBandLastTextValues(lLastDsgnDataBand);
-
-                    CommonRuntimeBandProcessing(lDsgnBand);
-                    lLastGroupCondition := TFPReportGroupHeaderBand(lRTBand).GroupConditionValue;
-                    if lDsgnBand.EvaluateVisibility = False then
-                    begin
-                      lRTPage.RemoveChild(lRTBand);
-                      lRTBand.Free;
-                      Continue; // process next band
-                    end;
-                    UpdateSpaceRemaining(lRTBand);
-                    if NoSpaceRemaining then
-                      Break;  { break out of FOR loop }
-                  end;
-                end;
-                lNewGroupHeader := False;
-                lDataHeaderPrinted := False;
-              end;  { lNewGroupHeader = True }
-            end;  { if lHasGroupBand }
-
-            { handle overflow possibly caused by Group Band just processed. }
-            if lOverflowed then
-              Continue;
-
-            for b := 0 to FBands.Count-1 do
-            begin
-              lDsgnBand := TFPReportCustomBand(FBands[b]);
-
-              { Process Master DataBand }
-              if (lDsgnBand is TFPReportCustomDataBand) then
-              begin
-                inc(lDataLevelStack);
-                if TFPReportCustomDataBand(lDsgnBand).HeaderBand <> nil then
-                  ShowDataHeaderBand(TFPReportCustomDataBand(lDsgnBand).HeaderBand);
-                ShowDataBand(lDsgnBand as TFPReportCustomDataBand);
-                ShowDetailBand(TFPReportCustomDataBand(lDsgnBand));
-                dec(lDataLevelStack);
-              end;
-            end; { Bands for loop }
-
-            FPageData.Next;
-          end;  { while not FPageData.EOF }
-
-          if not (TwoPass and IsFirstPass) then
-          begin
-            if lNewColumn then
-              StartNewColumn;
-
-            if lNewPage then
-              StartNewPage;
-
-            { handle overflowed bands. Remove from old page, add to new page }
-            if lOverflowed then
-              HandleOverflowed;
-
-            // only print if we actually had data
-            if (FPageData.RecNo > 1) then
-            begin
-              for b := 0 to FBands.Count-1 do
-              begin
-                lDsgnBand := TFPReportCustomBand(FBands[b]);
-                if lDsgnBand is TFPReportCustomDataBand then
-                  if TFPReportCustomDataBand(lDsgnBand).FooterBand <> nil then
-                    ShowDataFooterBand(TFPReportCustomDataBand(lDsgnBand).FooterBand);
-              end;
-            end;
-
-            { Process ColumnFooterBand as needed }
-            if lMultiColumn then
-            begin
-              lNewRTColumnFooterBand := TFPReportCustomColumnFooterBand(lFooterList.Find(TFPReportCustomColumnFooterBand));
-              if Assigned(lNewRTColumnFooterBand) then
-              begin
-                LayoutColumnFooterBand(lRTPage, lNewRTColumnFooterBand);
-              end;
-            end;
-
-            { ColumnFooter could have caused a new column or page }
-            if lNewColumn then
-              StartNewColumn;
-
-            if lNewPage then
-              StartNewPage;
-
-            { handle overflowed bands. Remove from old page, add to new page }
-            if lOverflowed then
-              HandleOverflowed;
-
-            if lHasGroupFooter then
-            begin
-              for b := 0 to FBands.Count-1 do
-              begin
-                lDsgnBand := TFPReportCustomBand(FBands[b]);
-                if lDsgnBand is TFPReportCustomGroupHeaderBand then
-                begin
-                  lDsgnBand:=(lDsgnBand as TFPReportCustomGroupHeaderBand).GroupFooter;
-                  { We are allowed to use design Layout.Height instead of RTLayout.Height
-                    because this band appears outside the data loop, thus memos will not
-                    grow. Height of the band is as it was at design time. }
-                  if lDsgnBand.Layout.Height > lSpaceLeft then
-                    StartNewPage;
-                  CommonRuntimeBandProcessing(lDsgnBand);
-                  UpdateSpaceRemaining(lRTBand);
-                  Break;
-                end;
-              end; { for FBands }
-            end;  { lHasGroupFooter }
-          end;
+  DoEndReport;
+end;
 
 
-          FPageData.Close;
+procedure TFPCustomReport.RenderReport(const AExporter: TFPReportExporter);
+begin
+  if not Assigned(AExporter) then
+    Exit;
+  StartRender;
+  try
+    AExporter.Report := self;
+    AExporter.Execute;
+  finally
+    EndRender;
+  end;
+end;
 
 
-        end;  { if Assigned(FPageData) }
+{$IFDEF gdebug}
+function TFPCustomReport.DebugPreparedPageAsJSON(const APageNo: Byte): string;
+var
+  rs: TFPReportStreamer;
+begin
+  if APageNo > RTObjects.Count-1 then
+    Exit;
+  rs := TFPReportJSONStreamer.Create(nil);
+  try
+    TFPReportCustomPage(RTObjects[APageNo]).WriteElement(rs);
+    Result := TFPReportJSONStreamer(rs).JSON.FormatJSON;
+  finally
+    rs.Free;
+  end;
+end;
+{$ENDIF}
 
 
-        if lHasReportSummaryBand then
-        begin
-          for b := 0 to FBands.Count-1 do
-          begin
-            lDsgnBand := TFPReportCustomBand(FBands[b]);
-            if lDsgnBand is TFPReportCustomSummaryBand then
-            begin
-              { We are allowed to use design Layout.Height instead of RTLayout.Height
-                because this band appears outside the data loop, thus memos will not
-                grow. Height of the band is as it was at design time. }
-              if (TFPReportCustomSummaryBand(lDsgnBand).StartNewPage) or (lDsgnBand.Layout.Height > lSpaceLeft) then
-                StartNewPage;
-              { Restore reference to lDsgnBand and SummaryBand, because StartNewPage
-                could have changed the value of lDsgnBand. }
-              lDsgnBand := TFPReportCustomBand(FBands[b]);
-              CommonRuntimeBandProcessing(lDsgnBand);
-              UpdateSpaceRemaining(lRTBand);
-            end;
-          end;
-        end;  { lHasReportSummaryBand }
+{ TFPReportMargins }
 
 
-      end; { for ... pages }
+procedure TFPReportMargins.SetBottom(const AValue: TFPReportUnits);
+begin
+  if FBottom = AValue then
+    Exit;
+  FBottom := AValue;
+  Changed;
+end;
 
 
-      FPageCount := RTObjects.Count;
-      if TwoPass and (lPassIdx = 1) then
-        EmptyRTObjects;
+procedure TFPReportMargins.SetLeft(const AValue: TFPReportUnits);
+begin
+  if FLeft = AValue then
+    Exit;
+  FLeft := AValue;
+  Changed;
+end;
 
 
-    end; { for ... lPassCount }
+procedure TFPReportMargins.SetRight(const AValue: TFPReportUnits);
+begin
+  if FRight = AValue then
+    Exit;
+  FRight := AValue;
+  Changed;
+end;
 
 
-    // DoProcessPass only substitutes cPageCountMarker by FPageCount.
-    // It is pointless to do so if we're doing 2 passes anyway
-    if UsePageCountMarker then
-      DoProcessTwoPass;
+procedure TFPReportMargins.SetTop(const AValue: TFPReportUnits);
+begin
+  if FTop = AValue then
+    Exit;
+  FTop := AValue;
+  Changed;
+end;
 
 
-  finally
-    FreeAndNil(lHeaderList);
-    FreeAndNil(lFooterList);
-    FreeAndNil(FBands);
-    SetLength(FPerDesignerPageCount, 0);
-  end;
+procedure TFPReportMargins.Changed;
+begin
+  if Assigned(FPage) then
+    FPage.MarginsChanged;
 end;
 end;
 
 
-procedure TFPCustomReport.DoBeginReport;
+constructor TFPReportMargins.Create(APage: TFPReportCustomPage);
 begin
 begin
-  if Assigned(FOnBeginReport) then
-    FOnBeginReport;
+  inherited Create;
+  FPage := APage;
 end;
 end;
 
 
-procedure TFPCustomReport.DoEndReport;
+procedure TFPReportMargins.Assign(Source: TPersistent);
+var
+  S: TFPReportMargins;
 begin
 begin
-  if Assigned(FOnEndReport) then
-    FOnEndReport;
+  if Source is TFPReportMargins then
+  begin
+    S := Source as TFPReportMargins;
+    FTop := S.Top;
+    FBottom := S.Bottom;
+    FLeft := S.Left;
+    FRight := S.Right;
+    Changed;
+  end
+  else
+    inherited Assign(Source);
 end;
 end;
 
 
-procedure TFPCustomReport.RestoreDefaultVariables;
+function TFPReportMargins.Equals(AMargins: TFPReportMargins): boolean;
+begin
+  Result := (AMargins = Self)
+    or ((Top = AMargins.Top) and (Left = AMargins.Left) and
+        (Right = AMargins.Right) and (Bottom = AMargins.Bottom));
+end;
 
 
-Var
-  I : Integer;
+procedure TFPReportMargins.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportMargins);
+begin
+  if (AOriginal = nil) then
+  begin
+    AWriter.WriteFloat('Top', Top);
+    AWriter.WriteFloat('Left', Left);
+    AWriter.WriteFloat('Bottom', Bottom);
+    AWriter.WriteFloat('Right', Right);
+  end
+  else
+  begin
+    AWriter.WriteFloatDiff('Top', Top, AOriginal.Top);
+    AWriter.WriteFloatDiff('Left', Left, AOriginal.Left);
+    AWriter.WriteFloatDiff('Bottom', Bottom, AOriginal.Bottom);
+    AWriter.WriteFloatDiff('Right', Right, AOriginal.Right);
+  end;
+end;
 
 
+procedure TFPReportMargins.ReadElement(AReader: TFPReportStreamer);
 begin
 begin
-  For I:=0 to FVariables.Count-1 do
-    FVariables[i].RestoreValue;
+  Top := AReader.ReadFloat('Top', Top);
+  Left := AReader.ReadFloat('Left', Left);
+  Bottom := AReader.ReadFloat('Bottom', Bottom);
+  Right := AReader.ReadFloat('Right', Right);
 end;
 end;
 
 
-procedure TFPCustomReport.InitializeDefaultExpressions;
+{ TFPReportCustomBand }
 
 
-Var
-  I : Integer;
-  V : TFPReportVariable;
-  lHasAggregates: Boolean;
+function TFPReportCustomBand.GetReportPage: TFPReportCustomPage;
+begin
+  Result := Parent as TFPReportCustomPage;
+end;
 
 
+function TFPReportCustomBand.GetFont: TFPReportFont;
 begin
 begin
-  FExpr.Clear;
-  FExpr.Identifiers.Clear;
-  FExpr.BuiltIns := [bcStrings,bcDateTime,bcMath,bcBoolean,bcConversion,bcData,bcVaria,bcUser, bcAggregate];
-  FExpr.Identifiers.AddDateTimeVariable('TODAY', Date);
-  FExpr.Identifiers.AddStringVariable('AUTHOR', Author);
-  FExpr.Identifiers.AddStringVariable('TITLE', Title);
-  FExpr.Identifiers.AddFunction('RecNo', 'I', '', @BuiltinExprRecNo);
-  FExpr.Identifiers.AddFunction('PageNo', 'I', '', @BuiltinGetPageNumber);
-  FExpr.Identifiers.AddFunction('PageNoPerDesignerPage', 'I', '', @BuiltInGetPageNoPerDesignerPage);
-  lHasAggregates:=false;
-  For I:=0 to FVariables.Count-1 do
+  if UseParentFont then
   begin
   begin
-    V:=FVariables[i];
-    V.SaveValue;
-    if V.Expression = '' then
-      FExpr.Identifiers.AddVariable(V.Name,V.DataType,@V.GetRTValue)
+    if Assigned(Owner) then
+      Result := TFPReportCustomPage(Owner).Font
     else
     else
-      lHasAggregates:=true;
-  end;
-  if lHasAggregates then
-    TwoPass:=true;
-  if UsePageCountMarker then
-    FExpr.Identifiers.AddFunction('PageCount', 'S', '', @BuiltinGetPageCount)
+    begin
+      FFont := TFPReportFont.Create;
+      Result := FFont;
+    end;
+  end
   else
   else
-    FExpr.Identifiers.AddFunction('PageCount', 'I', '', @BuiltinGetPageCount);
+    Result := FFont;
 end;
 end;
 
 
-procedure TFPCustomReport.InitializeExpressionVariables(const APage: TFPReportCustomPage; const AData: TFPReportData);
+function TFPReportCustomBand.IsStringValueZero(const AValue: string): boolean;
 var
 var
-  i: Integer;
-  f: string;
-  r: TResultType;
-  d: string;
-  v: TFPReportVariable;
-
-  function ReportKindToResultType(const AType: TFPReportFieldKind): TResultType;
+  lIntVal: integer;
+  lFloatVal: double;
+begin
+  Result := False;
+  if TryStrToInt(AValue, lIntVal) then
   begin
   begin
-    case AType of
-      rfkString:      Result := rtString;
-      rfkBoolean:     Result := rtBoolean;
-      rfkInteger:     Result := rtInteger;
-      rfkFloat:       Result := rtFloat;
-      rfkDateTime:    Result := rtDateTime;
-      rfkStream:      Result := rtString; //  TODO:  What do we do here?????
-      else
-        Result := rtString;
-    end;
+    if lIntVal = 0 then
+      Result := True;
+  end
+  else if TryStrToFloat(AValue, lFloatVal) then
+  begin
+    if lFloatVal = 0 then
+      Result := True;
   end;
   end;
+end;
 
 
+procedure TFPReportCustomBand.SetChildBand(AValue: TFPReportChildBand);
+var
+  b: TFPReportCustomBand;
+begin
+  if FChildBand = AValue then
+    Exit;
+  FChildBand := AValue;
+  b := FChildBand;
+  while b <> nil do
+  begin
+    b := b.ChildBand;
+    if b = self then
+      raise EReportError.Create(SErrChildBandCircularReference);
+  end;
+end;
 
 
+procedure TFPReportCustomBand.ApplyStretchMode;
+var
+  h: TFPReportUnits;
+  c: TFPReportElement;
+  i: integer;
 begin
 begin
-  {$ifdef gdebug}
-  writeln('********** TFPCustomReport.InitializeExpressionVariables');
-  {$endif}
-  F:='';
-  For I:=0 to FExpr.Identifiers.Count-1 do
-    f:=f+FExpr.Identifiers[i].Name+'; ';
-  for i := 0 to AData.DataFields.Count-1 do
+  h := RTLayout.Height;
+  for i := 0 to ChildCount-1 do
   begin
   begin
-    d := AData.Name;
-    f := AData.DataFields[i].FieldName;
-    r := ReportKindToResultType(AData.DataFields[i].FieldKind);
-    if d <> '' then
-      begin
-      {$ifdef gdebug}
-      writeln('registering (dotted name)... '+ d+'.'+f);
-      {$endif}
-      FExpr.Identifiers.AddVariable(d+'.'+f, r, @DoGetExpressionVariableValue);
-      end
-    else
-      begin
-      {$ifdef gdebug}
-      writeln('registering... '+ f);
-      {$endif}
-      FExpr.Identifiers.AddVariable(f, r, @DoGetExpressionVariableValue);
-      end;
-  end;
-  if APage.Data = AData then
-  begin
-    For I:=0 to FVariables.Count-1 do
-    begin
-      v:=FVariables[I];
-      if v.Expression<>'' then
-      begin
-        FExpr.Expression:=v.Expression;
-        FExpr.ExtractNode(v.FExpressionNode);
-        v.FIsAggregate:=v.FExpressionNode.IsAggregate;
-        if v.FExpressionNode.HasAggregate and
-        not v.FExpressionNode.IsAggregate then
-          raise EReportError.CreateFmt(SErrExprVarisbleAggregateOnWrongLevel, [v.FExpressionNode.AsString]);
-        if not v.FIsAggregate then begin
-          v.FResetType:=rtNone;
-          v.FResetValueExpression:='';
-        end;
-      end;
-      if v.ResetValueExpression<>'' then
-      begin
-        FExpr.Expression := v.ResetValueExpression;
-        FExpr.ExtractNode(v.FResetValueExpressionNode);
-      end;
-    end;
-    For I:=0 to FVariables.Count-1 do
-    begin
-      v:=FVariables[I];
-      if v.Expression<>'' then
-        FExpr.Identifiers.AddVariable(v.Name, v.DataType, @v.GetRTExpressionValue);
-    end;
+    c := Child[i];
+    if c.RTLayout.Top + c.RTLayout.Height > h then
+      h := c.RTLayout.Top + c.RTLayout.Height;
   end;
   end;
+  RTLayout.Height := h;
 end;
 end;
 
 
-procedure TFPCustomReport.CacheMemoExpressions(const APageIdx: integer; const AData: TFPReportData);
-var
-  b: integer;
-  c: integer;
-  m: TFPReportCustomMemo;
+procedure TFPReportCustomBand.SetFont(AValue: TFPReportFont);
 begin
 begin
-  for b := 0 to Pages[APageIdx].BandCount-1 do
+  if UseParentFont then
+    UseParentFont := False;
+  FFont.Assign(AValue);
+  Changed;
+end;
+
+procedure TFPReportCustomBand.SetUseParentFont(AValue: boolean);
+begin
+  if FUseParentFont = AValue then
+    Exit;
+  FUseParentFont := AValue;
+  if FUseParentFont then
+    FreeAndNil(FFont)
+  else
   begin
   begin
-    if Pages[APageIdx].Bands[b] is TFPReportCustomBandWithData then
-    begin
-      if TFPReportCustomBandWithData(Pages[APageIdx].Bands[b]).Data <> AData then
-        Continue;  // band is from a different data-loop
-    end;
+    FFont := TFPReportFont.Create;
+    if Assigned(Owner) then
+      FFont.Assign(TFPReportCustomPage(Owner).Font);
+  end;
+  Changed;
+end;
 
 
-    for c := 0 to Pages[APageIdx].Bands[b].ChildCount-1 do
-      if Pages[APageIdx].Bands[b].Child[c] is TFPReportCustomMemo then
-      begin
-        m := TFPReportCustomMemo(Pages[APageIdx].Bands[b].Child[c]);
-        m.ParseText;
-      end;
-  end; { bands }
+procedure TFPReportCustomBand.SetVisibleOnPage(AValue: TFPReportVisibleOnPage);
+begin
+  if FVisibleOnPage = AValue then
+    Exit;
+  FVisibleOnPage := AValue;
+  Changed;
 end;
 end;
 
 
-constructor TFPCustomReport.Create(AOwner: TComponent);
+function TFPReportCustomBand.GetReportBandName: string;
 begin
 begin
-  inherited Create(AOwner);
-  FReportData:=CreateReportData;
-  FRTObjects := TFPList.Create;
-  FImages := CreateImages;
-  FVariables:=CreateVariables;
-  FRTCurPageIdx := -1;
-  FDateCreated := Now;
-  FTwoPass := False;
-  FIsFirstPass := False;
+  Result := 'FPCustomReportBand';
 end;
 end;
 
 
-function TFPCustomReport.CreateImages: TFPReportImages;
+function TFPReportCustomBand.GetData: TFPReportData;
+begin
+  result := nil;
+end;
 
 
+procedure TFPReportCustomBand.SetDataFromName(AName: String);
 begin
 begin
-  Result:=TFPReportImages.Create(self, TFPReportImageItem);
+  // Do nothing
 end;
 end;
 
 
-function TFPCustomReport.CreateVariables: TFPReportVariables;
+procedure TFPReportCustomBand.SetParent(const AValue: TFPReportElement);
+begin
+  if not ((AValue = nil) or (AValue is TFPReportCustomPage)) then
+    ReportError(SErrNotAReportPage, [AValue.ClassName, AValue.Name]);
+  inherited SetParent(AValue);
+end;
 
 
+procedure TFPReportCustomBand.CreateRTLayout;
 begin
 begin
-  Result:=TFPReportVariables.Create(Self,TFPReportVariable);
+  inherited CreateRTLayout;
+  FRTLayout.Left := Page.Layout.Left;
 end;
 end;
 
 
-function TFPCustomReport.CreateReportData : TFPReportDataCollection;
+procedure TFPReportCustomBand.PrepareObjects;
+
+var
+  lRTPage: TFPReportCustomPage;
+  i: integer;
+  m: TFPReportMemo;
+  cb: TFPReportCheckbox;
+  img: TFPReportCustomImage;
+  s: string;
+  c: integer;
+  n: TFPExprNode;
+  nIdx: integer;
 
 
 begin
 begin
-  Result:=TFPReportDataCollection.Create(TFPReportDataItem);
+  i := Page.Report.FRTCurPageIdx;
+  if Assigned(Page.Report.RTObjects[i]) then
+  begin
+    lRTPage := TFPReportCustomPage(Page.Report.RTObjects[i]);
+    Page.Report.FRTCurBand := TFPReportBandClass(ClassType).Create(lRTPage);
+    Page.Report.FRTCurBand.Assign(self);
+    Page.Report.FRTCurBand.CreateRTLayout;
+  end;
+  inherited PrepareObjects;
+
+  if self is TFPReportGroupHeaderBand then
+    TFPReportGroupHeaderBand(Page.Report.FRTCurBand).CalcGroupConditionValue;
+
+  if Assigned(FChildren) then
+  begin
+    for c := 0 to Page.Report.FRTCurBand.ChildCount-1 do
+    begin
+      if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomMemo then
+      begin
+        m := TFPReportMemo(Page.Report.FRTCurBand.Child[c]);
+        if moDisableExpressions in m.Options then
+          Continue; // nothing further to do
+        m.ExpandExpressions;
+        // visibility handling
+        if moHideZeros in m.Options then
+        begin
+          if IsStringValueZero(m.Text) then
+          begin
+            m.Visible := False;
+            Continue;
+          end;
+        end;
+        if moSuppressRepeated in m.Options then
+        begin
+          if m.Original.FLastText = m.Text then
+          begin
+            m.Visible := False;
+            Continue;
+          end
+          else
+            m.Original.FLastText := m.Text;
+        end;
+        // aggregate handling
+        for nIdx := 0 to Length(m.Original.ExpressionNodes)-1 do
+        begin
+          n := m.Original.ExpressionNodes[nIdx].ExprNode;
+          if not Assigned(n) then
+            Continue;
+          if n.HasAggregate then
+          begin
+            if moNoResetAggregateOnPrint in m.Options then
+            begin
+              // do nothing
+            end
+                 // apply memo.Options rules if applicable
+            else if ((self is TFPReportCustomPageHeaderBand) and (moResetAggregateOnPage in m.Options))
+                  or ((self is TFPReportCustomColumnHeaderBand) and (moResetAggregateOnColumn in m.Options))
+                  or ((self is TFPReportCustomGroupHeaderBand) and (moResetAggregateOnGroup in m.Options)) then
+                n.InitAggregate
+                 // apply Page/Column/Group/Data footer rule
+            else if (self is TFPReportCustomPageFooterBand)
+                  or (self is TFPReportCustomColumnFooterBand)
+                  or (self is TFPReportCustomGroupFooterBand)
+                  or (self is TFPReportCustomDataFooterBand) then
+                n.InitAggregate
+            else
+              // default rule - reset on print. applies to all memos
+              n.InitAggregate;
+          end;
+        end;
+      end
+      else if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomCheckbox then
+      begin
+        cb := TFPReportCheckbox(Page.Report.FRTCurBand.Child[c]);
+        s := ExpandMacro(cb.Expression, True);
+        cb.FTestResult := StrToBoolDef(s, False);
+      end
+      else if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomImage then
+      begin
+        img := TFPReportCustomImage(Page.Report.FRTCurBand.Child[c]);
+        if (img.FieldName <> '') and Assigned(GetData) then
+          img.LoadDBData(GetData);
+      end;
+    end; { for c := 0 to ... }
+  end;  { if Assigned(FChildren) ... }
 end;
 end;
 
 
-destructor TFPCustomReport.Destroy;
+procedure TFPReportCustomBand.RecalcLayout;
 begin
 begin
-  EmptyRTObjects;
-  FreeAndNil(FReportData);
-  FreeAndNil(FRTObjects);
-  FreeAndNil(FPages);
-  FreeAndNil(FExpr);
-  FreeAndNil(FReferenceList);
-  FreeAndNil(FImages);
-  FreeAndNil(FVariables);
-  inherited Destroy;
+  inherited RecalcLayout;
+  if StretchMode <> smDontStretch then
+    ApplyStretchMode;
 end;
 end;
 
 
-procedure TFPCustomReport.SaveDataToNames;
-
-Var
-  I : Integer;
+procedure TFPReportCustomBand.Assign(Source: TPersistent);
+var
+  E: TFPReportCustomBand;
+begin
+  inherited Assign(Source);
+  if Source is TFPReportCustomBand then
+  begin
+    E := TFPReportCustomBand(Source);
+    FChildBand := E.ChildBand;
+    FStretchMode := E.StretchMode;
+    FVisibleOnPage := E.VisibleOnPage;
+    UseParentFont := E.UseParentFont;
+    if not UseParentFont then
+      Font.Assign(E.Font);
+  end;
+end;
 
 
+class function TFPReportCustomBand.ReportBandType: TFPReportBandType;
 begin
 begin
-  For I:=0 to PageCount-1 do
-    Pages[i].SaveDataToNames;
+  Result:=btUnknown;
 end;
 end;
 
 
-procedure TFPCustomReport.RestoreDataFromNames;
-Var
-  I : Integer;
+procedure TFPReportCustomBand.BeforePrint;
+var
+  i: integer;
+  c: TFPReportElement;
+begin
+  inherited BeforePrint;
+  if Visible = false then
+    exit;
+  if Assigned(FChildren) then
+  begin
+    for i := 0 to FChildren.Count-1 do
+    begin
+      c := Child[i];
+      c.BeforePrint;
+    end;
+  end;
+end;
 
 
+procedure TFPReportCustomBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
 begin
 begin
-  For I:=0 to PageCount-1 do
-    Pages[i].RestoreDataFromNames;
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  AWriter.WriteBoolean('UseParentFont', UseParentFont);
+  if not UseParentFont then
+  begin
+    AWriter.WriteString('FontName', Font.Name);
+    AWriter.WriteInteger('FontSize', Font.Size);
+    AWriter.WriteInteger('FontColor', Font.Color);
+  end;
 end;
 end;
 
 
-procedure TFPCustomReport.AddPage(APage: TFPReportCustomPage);
+constructor TFPReportCustomBand.Create(AOwner: TComponent);
 begin
 begin
-  if not Assigned(FPages) then
-  begin
-    FPages := TFPList.Create;
-    FPages.Add(APage);
-  end
-  else if FPages.IndexOf(APage) = -1 then
-    FPages.Add(APage);
+  inherited Create(AOwner);
+  FVisibleOnPage := vpAll;
+  FUseParentFont := True;
+  FFont := nil
 end;
 end;
 
 
-procedure TFPCustomReport.RemovePage(APage: TFPReportCustomPage);
+destructor TFPReportCustomBand.Destroy;
 begin
 begin
-  if Assigned(FPages) then
-    FPages.Remove(APage);
+  FreeAndNil(FFont);
+  inherited Destroy;
 end;
 end;
 
 
-procedure TFPCustomReport.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
-var
-  i: integer;
+procedure TFPReportCustomBand.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
 begin
 begin
-  // ignore AOriginal here as we don't support whole report diffs, only element diffs
-  AWriter.PushElement('Report');
+  AWriter.PushElement(GetReportBandName);
   try
   try
     inherited WriteElement(AWriter, AOriginal);
     inherited WriteElement(AWriter, AOriginal);
-    // local properties
-    AWriter.WriteString('Title', Title);
-    AWriter.WriteString('Author', Author);
-    AWriter.WriteDateTime('DateCreated', DateCreated);
-    // now the design-time images
-    AWriter.PushElement('Images');
-    try
-      for i := 0 to Images.Count-1 do
-      begin
-        AWriter.PushElement(IntToStr(i)); // use image index as identifier
-        try
-          Images[i].WriteElement(AWriter);
-        finally
-          AWriter.PopElement;
-        end;
-      end;
-    finally
-      AWriter.PopElement;
-    end;
-    // now the pages
-    AWriter.PushElement('Pages');
-    try
-      for i := 0 to PageCount - 1 do
-      begin
-        AWriter.PushElement(IntToStr(i)); // use page index as identifier
-        try
-          Pages[i].WriteElement(AWriter);
-        finally
-          AWriter.PopElement;
-        end;
-      end;
-    finally
-      AWriter.PopElement;
-    end;
+    if Assigned(ChildBand) then
+      AWriter.WriteString('ChildBand', ChildBand.Name);
+    if Assigned(GetData) then
+      AWriter.WriteString('Data', GetData.Name);
+    AWriter.WriteString('VisibleOnPage', VisibleOnPageToString(FVisibleOnPage));
   finally
   finally
     AWriter.PopElement;
     AWriter.PopElement;
   end;
   end;
-  // TODO: Implement writing OnRenderReport, OnBeginReport, OnEndReport
 end;
 end;
 
 
-procedure TFPCustomReport.ReadElement(AReader: TFPReportStreamer);
+procedure TFPReportCustomBand.ReadElement(AReader: TFPReportStreamer);
 var
 var
   E: TObject;
   E: TObject;
-  i: integer;
-  p: TFPReportPage;
-  lImgItem: TFPReportImageItem;
+  s: string;
 begin
 begin
-  ClearReferenceList;
-  E := AReader.FindChild('Report');
+  E := AReader.FindChild(GetReportBandName);
   if Assigned(E) then
   if Assigned(E) then
   begin
   begin
     AReader.PushElement(E);
     AReader.PushElement(E);
     try
     try
       inherited ReadElement(AReader);
       inherited ReadElement(AReader);
-      FTitle := AReader.ReadString('Title', Title);
-      FAuthor := AReader.ReadString('Author', Author);
-      FDateCreated := AReader.ReadDateTime('DateCreated', Now);
-
-      E := AReader.FindChild('Images');
-      if Assigned(E) then
-      begin
-        AReader.PushElement(E);
-        for i := 0 to AReader.ChildCount-1 do
-        begin
-          E := AReader.GetChild(i);
-          AReader.PushElement(E); // child index is the identifier
-          try
-            lImgItem := Images.AddImageItem;
-            lImgItem.ReadElement(AReader);
-          finally
-            AReader.PopElement;
-          end;
-        end; { for i }
-        AReader.PopElement;
-      end;  { images }
-
-      E := AReader.FindChild('Pages');
-      if Assigned(E) then
+      s := AReader.ReadString('ChildBand', '');
+      if s <> '' then
+        Page.Report.AddReference(self.Name, s);
+//        Page.Report.AddReference(self, 'ChildBand', s);
+      FVisibleOnPage := StringToVisibleOnPage(AReader.ReadString('VisibleOnPage', 'vpAll'));
+      FUseParentFont := AReader.ReadBoolean('UseParentFont', UseParentFont);
+      if not FUseParentFont then
       begin
       begin
-        AReader.PushElement(E);
-        for i := 0 to AReader.ChildCount-1 do
-        begin
-          E := AReader.GetChild(i);
-          AReader.PushElement(E); // child index is the identifier
-          try
-            p := TFPReportPage.Create(self);
-            p.ReadElement(AReader);
-            AddPage(p);
-          finally
-            AReader.PopElement;
-          end;
-        end;  { for i }
-        AReader.PopElement;
-      end; { pages }
+        Font.Name := AReader.ReadString('FontName', Font.Name);
+        Font.Size := AReader.ReadInteger('FontSize', Font.Size);
+        Font.Color := AReader.ReadInteger('FontColor', Font.Color);
+      end;
 
 
-      // TODO: Implement reading OnRenderReport, OnBeginReport, OnEndReport
+      // TODO: Read Data information
+      S:=AReader.ReadString('Data','');
+      if (S<>'') then
+        SetDataFromName(S);
     finally
     finally
       AReader.PopElement;
       AReader.PopElement;
     end;
     end;
   end;
   end;
-  FixupReferences;
 end;
 end;
 
 
-procedure TFPCustomReport.StartRender;
+{ TFPReportCustomBandWithData }
+
+procedure TFPReportCustomBandWithData.SetData(const AValue: TFPReportData);
 begin
 begin
-  inherited StartRender;
-  DoBeforeRenderReport;
+  if FData = AValue then
+    Exit;
+  if Assigned(FData) then
+    FData.RemoveFreeNotification(Self);
+  FData := AValue;
+  if Assigned(FData) then
+    FData.FreeNotification(Self);
 end;
 end;
 
 
-procedure TFPCustomReport.EndRender;
+procedure TFPReportCustomBandWithData.SaveDataToNames;
 begin
 begin
-  inherited EndRender;
-  DoAfterRenderReport;
+  inherited SaveDataToNames;
+  if Assigned(FData) then
+    FDataName:=FData.Name
+  else
+    FDataName:='';
 end;
 end;
 
 
-function TFPCustomReport.FindRecursive(const AName: string): TFPReportElement;
-var
-  p, b, c: integer;
-begin
-  Result := nil;
-  if AName = '' then
-    Exit;
-  for p := 0 to PageCount-1 do
-  begin
-    for b := 0 to Pages[p].BandCount-1 do
-    begin
-      if SameText(Pages[p].Bands[b].Name, AName) then
-        Result := Pages[p].Bands[b];
-      if Assigned(Result) then
-        Exit;
+procedure TFPReportCustomBandWithData.ResolveDataName;
 
 
-      for c := 0 to Pages[p].Bands[b].ChildCount-1 do
-      begin
-        if SameText(Pages[p].Bands[b].Child[c].Name, AName) then
-          Result := Pages[p].Bands[b].Child[c];
-        if Assigned(Result) then
-          Exit;
-      end;
-    end;
-  end;
+begin
+  if (FDataName<>'') then
+    Data:=Report.ReportData.FindReportData(FDataName)
+  else
+    Data:=Nil;
 end;
 end;
+procedure TFPReportCustomBandWithData.RestoreDataFromNames;
 
 
-procedure TFPCustomReport.RunReport;
 begin
 begin
-  DoBeginReport;
+  inherited RestoreDataFromNames;
+  ResolveDataName;
+end;
 
 
-  StartLayout;
-  FExpr := TFPexpressionParser.Create(nil);
-  try
-    InitializeDefaultExpressions;
-    DoPrepareReport;
-  finally
-    RestoreDefaultVariables;
-    FreeAndNil(FExpr);
-  end;
-  EndLayout;
+function TFPReportCustomBandWithData.GetData: TFPReportData;
+begin
+  Result := FData;
+end;
 
 
-  DoEndReport;
+procedure TFPReportCustomBandWithData.SetDataFromName(AName: String);
+begin
+  FDataName:=AName;
+  ResolveDataName;
 end;
 end;
 
 
-procedure TFPCustomReport.RenderReport(const AExporter: TFPReportExporter);
+procedure TFPReportCustomBandWithData.Notification(AComponent: TComponent; Operation: TOperation);
 begin
 begin
-  if not Assigned(AExporter) then
-    Exit;
-  StartRender;
-  try
-    AExporter.Report := self;
-    AExporter.Execute;
-  finally
-    EndRender;
+  if Operation = opRemove then
+  begin
+    if AComponent = FData then
+      FData := nil;
   end;
   end;
+  inherited Notification(AComponent, Operation);
 end;
 end;
 
 
-{$IFDEF gdebug}
-function TFPCustomReport.DebugPreparedPageAsJSON(const APageNo: Byte): string;
-var
-  rs: TFPReportStreamer;
+constructor TFPReportCustomBandWithData.Create(AOwner: TComponent);
 begin
 begin
-  if APageNo > RTObjects.Count-1 then
-    Exit;
-  rs := TFPReportJSONStreamer.Create(nil);
-  try
-    TFPReportCustomPage(RTObjects[APageNo]).WriteElement(rs);
-    Result := TFPReportJSONStreamer(rs).JSON.FormatJSON;
-  finally
-    rs.Free;
+  FData := nil;
+  inherited Create(AOwner);
+end;
+
+{ TFPReportCustomGroupFooterBand }
+
+procedure TFPReportCustomGroupFooterBand.SetGroupHeader(const AValue: TFPReportCustomGroupHeaderBand);
+begin
+  if FGroupHeader = AValue then
+    Exit;
+  if Assigned(FGroupHeader) then
+  begin
+    FGroupHeader.FGroupFooter := nil;
+    FGroupHeader.RemoveFreeNotification(Self);
+  end;
+  FGroupHeader := AValue;
+  if Assigned(FGroupHeader) then
+  begin
+    FGroupHeader.FGroupFooter := Self;
+    FGroupHeader.FreeNotification(Self);
   end;
   end;
 end;
 end;
-{$ENDIF}
 
 
-{ TFPReportMargins }
+function TFPReportCustomGroupFooterBand.GetReportBandName: string;
+begin
+  Result := 'GroupFooterBand';
+end;
 
 
-procedure TFPReportMargins.SetBottom(const AValue: TFPReportUnits);
+procedure TFPReportCustomGroupFooterBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
 begin
 begin
-  if FBottom = AValue then
-    Exit;
-  FBottom := AValue;
-  Changed;
+  inherited DoWriteLocalProperties(AWriter, AOriginal);
+  if Assigned(GroupHeader) then
+    AWriter.WriteString('GroupHeader', GroupHeader.Name);
 end;
 end;
 
 
-procedure TFPReportMargins.SetLeft(const AValue: TFPReportUnits);
+procedure TFPReportCustomGroupFooterBand.Notification(AComponent: TComponent; Operation: TOperation);
 begin
 begin
-  if FLeft = AValue then
-    Exit;
-  FLeft := AValue;
-  Changed;
+  if (Operation = opRemove) and (AComponent = FGroupHeader) then
+    FGroupHeader := nil;
+  inherited Notification(AComponent, Operation);
 end;
 end;
 
 
-procedure TFPReportMargins.SetRight(const AValue: TFPReportUnits);
+procedure TFPReportCustomGroupFooterBand.ReadElement(AReader: TFPReportStreamer);
+var
+  s: string;
+//  c: TFPReportElement;
 begin
 begin
-  if FRight = AValue then
+//  c := nil;
+  inherited ReadElement(AReader);
+  s := AReader.ReadString('GroupHeader', '');
+  if s = '' then
     Exit;
     Exit;
-  FRight := AValue;
-  Changed;
+  // TODO: recursively search Page.Report for the GroupHeader
+  //c := Page.Report.FindComponent(s);
+  //if Assigned(c) then
+  //  FGroupHeader := TFPReportCustomGroupHeaderBand(c);
 end;
 end;
 
 
-procedure TFPReportMargins.SetTop(const AValue: TFPReportUnits);
+class function TFPReportCustomGroupFooterBand.ReportBandType: TFPReportBandType;
 begin
 begin
-  if FTop = AValue then
-    Exit;
-  FTop := AValue;
-  Changed;
+  Result:=btGroupFooter;
 end;
 end;
 
 
-procedure TFPReportMargins.Changed;
+
+{ TFPReportImageItem }
+
+function TFPReportImageItem.GetHeight: Integer;
 begin
 begin
-  if Assigned(FPage) then
-    FPage.MarginsChanged;
+  If Assigned(FImage) then
+    Result:=FImage.Height
+  else
+    Result:=FHeight;
 end;
 end;
 
 
-constructor TFPReportMargins.Create(APage: TFPReportCustomPage);
+function TFPReportImageItem.GetStreamed: TBytes;
 begin
 begin
-  inherited Create;
-  FPage := APage;
+  if Length(FStreamed)=0 then
+    CreateStreamedData;
+  Result:=FStreamed;
 end;
 end;
 
 
-procedure TFPReportMargins.Assign(Source: TPersistent);
-var
-  S: TFPReportMargins;
+function TFPReportImageItem.GetWidth: Integer;
 begin
 begin
-  if Source is TFPReportMargins then
-  begin
-    S := Source as TFPReportMargins;
-    FTop := S.Top;
-    FBottom := S.Bottom;
-    FLeft := S.Left;
-    FRight := S.Right;
-    Changed;
-  end
+  If Assigned(FImage) then
+    Result:=FImage.Width
   else
   else
-    inherited Assign(Source);
+    Result:=FWidth;
 end;
 end;
 
 
-function TFPReportMargins.Equals(AMargins: TFPReportMargins): boolean;
+procedure TFPReportImageItem.SetImage(AValue: TFPCustomImage);
 begin
 begin
-  Result := (AMargins = Self)
-    or ((Top = AMargins.Top) and (Left = AMargins.Left) and
-        (Right = AMargins.Right) and (Bottom = AMargins.Bottom));
+  if FImage=AValue then Exit;
+  FImage:=AValue;
+  SetLength(FStreamed,0);
 end;
 end;
 
 
-procedure TFPReportMargins.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportMargins);
+procedure TFPReportImageItem.SetStreamed(AValue: TBytes);
 begin
 begin
-  if (AOriginal = nil) then
-  begin
-    AWriter.WriteFloat('Top', Top);
-    AWriter.WriteFloat('Left', Left);
-    AWriter.WriteFloat('Bottom', Bottom);
-    AWriter.WriteFloat('Right', Right);
-  end
-  else
-  begin
-    AWriter.WriteFloatDiff('Top', Top, AOriginal.Top);
-    AWriter.WriteFloatDiff('Left', Left, AOriginal.Left);
-    AWriter.WriteFloatDiff('Bottom', Bottom, AOriginal.Bottom);
-    AWriter.WriteFloatDiff('Right', Right, AOriginal.Right);
+  If AValue=FStreamed then exit;
+  SetLength(FStreamed,0);
+  FStreamed:=AValue;
+end;
+
+procedure TFPReportImageItem.LoadPNGFromStream(AStream: TStream);
+var
+  PNGReader: TFPReaderPNG;
+begin
+  if not Assigned(AStream) then
+    Exit;
+
+  { we use Image property here so it frees any previous image }
+  if Assigned(FImage) then
+    FreeAndNil(FImage);
+  FImage := TFPCompactImgRGBA8Bit.Create(0, 0);
+  try
+    PNGReader := TFPReaderPNG.Create;
+    try
+      FImage.LoadFromStream(AStream, PNGReader); // auto size image
+    finally
+      PNGReader.Free;
+    end;
+  except
+    FreeAndNil(FImage);
   end;
   end;
 end;
 end;
 
 
-procedure TFPReportMargins.ReadElement(AReader: TFPReportStreamer);
+constructor TFPReportImageItem.Create(ACollection: TCollection);
 begin
 begin
-  Top := AReader.ReadFloat('Top', Top);
-  Left := AReader.ReadFloat('Left', Left);
-  Bottom := AReader.ReadFloat('Bottom', Bottom);
-  Right := AReader.ReadFloat('Right', Right);
+  inherited Create(ACollection);
+  FOwnsImage := True;
 end;
 end;
 
 
-{ TFPReportCustomBand }
+destructor TFPReportImageItem.Destroy;
+begin
+  if FOwnsImage then
+    FreeAndNil(FImage);
+  inherited Destroy;
+end;
 
 
-function TFPReportCustomBand.GetReportPage: TFPReportCustomPage;
+procedure TFPReportImageItem.CreateStreamedData;
+Var
+  X, Y: Integer;
+  C: TFPColor;
+  MS: TMemoryStream;
+  Str: TStream;
+  CWhite: TFPColor; // white color
 begin
 begin
-  Result := Parent as TFPReportCustomPage;
+  FillMem(@CWhite, SizeOf(CWhite), $FF);
+  FWidth:=Image.Width;
+  FHeight:=Image.Height;
+  Str := nil;
+  MS := TMemoryStream.Create;
+  try
+    Str := MS;
+    for Y:=0 to FHeight-1 do
+      for X:=0 to FWidth-1 do
+        begin
+        C:=Image.Colors[x,y];
+        if C.alpha < $FFFF then // remove alpha channel - assume white background
+          C := AlphaBlend(CWhite, C);
+
+        Str.WriteByte(C.Red shr 8);
+        Str.WriteByte(C.Green shr 8);
+        Str.WriteByte(C.blue shr 8);
+        end;
+    if Str<>MS then
+      Str.Free;
+    Str := nil;
+    SetLength(FStreamed, MS.Size);
+    MS.Position := 0;
+    if MS.Size>0 then
+      MS.ReadBuffer(FStreamed[0], MS.Size);
+  finally
+    Str.Free;
+    MS.Free;
+  end;
 end;
 end;
 
 
-function TFPReportCustomBand.GetFont: TFPReportFont;
+function TFPReportImageItem.WriteImageStream(AStream: TStream): UInt64;
+var
+  Img: TBytes;
 begin
 begin
-  if UseParentFont then
-  begin
-    if Assigned(Owner) then
-      Result := TFPReportCustomPage(Owner).Font
-    else
-    begin
-      FFont := TFPReportFont.Create;
-      Result := FFont;
-    end;
-  end
-  else
-    Result := FFont;
+  Img := StreamedData;
+  Result := Length(Img);
+  AStream.WriteBuffer(Img[0],Result);
 end;
 end;
 
 
-function TFPReportCustomBand.IsStringValueZero(const AValue: string): boolean;
+function TFPReportImageItem.Equals(AImage: TFPCustomImage): boolean;
 var
 var
-  lIntVal: integer;
-  lFloatVal: double;
+  x, y: Integer;
 begin
 begin
-  Result := False;
-  if TryStrToInt(AValue, lIntVal) then
-  begin
-    if lIntVal = 0 then
-      Result := True;
-  end
-  else if TryStrToFloat(AValue, lFloatVal) then
-  begin
-    if lFloatVal = 0 then
-      Result := True;
-  end;
+  Result := True;
+  for x := 0 to Image.Width-1 do
+    for y := 0 to Image.Height-1 do
+      if Image.Pixels[x, y] <> AImage.Pixels[x, y] then
+      begin
+        Result := False;
+        Exit;
+      end;
 end;
 end;
 
 
-procedure TFPReportCustomBand.SetChildBand(AValue: TFPReportChildBand);
+procedure TFPReportImageItem.WriteElement(AWriter: TFPReportStreamer);
 var
 var
-  b: TFPReportCustomBand;
+  ms: TMemoryStream;
+  png: TFPWriterPNG;
 begin
 begin
-  if FChildBand = AValue then
-    Exit;
-  FChildBand := AValue;
-  b := FChildBand;
-  while b <> nil do
+  if Assigned(Image) then
   begin
   begin
-    b := b.ChildBand;
-    if b = self then
-      raise EReportError.Create(SErrChildBandCircularReference);
+    ms := TMemoryStream.Create;
+    try
+      png := TFPWriterPNG.create;
+      png.Indexed := False;
+      Image.SaveToStream(ms, png);
+      ms.Position := 0;
+      AWriter.WriteStream('ImageData', ms);
+    finally
+      png.Free;
+      ms.Free;
+    end;
   end;
   end;
 end;
 end;
 
 
-procedure TFPReportCustomBand.ApplyStretchMode;
+procedure TFPReportImageItem.ReadElement(AReader: TFPReportStreamer);
 var
 var
-  h: TFPReportUnits;
-  c: TFPReportElement;
-  i: integer;
+  ms: TMemoryStream;
 begin
 begin
-  h := RTLayout.Height;
-  for i := 0 to ChildCount-1 do
-  begin
-    c := Child[i];
-    if c.RTLayout.Top + c.RTLayout.Height > h then
-      h := c.RTLayout.Top + c.RTLayout.Height;
+  ms := TMemoryStream.Create;
+  try
+    if AReader.ReadStream('ImageData', ms) then
+    begin
+      ms.Position := 0;
+      LoadPNGFromStream(ms);
+    end;
+  finally
+    ms.Free;
   end;
   end;
-  RTLayout.Height := h;
 end;
 end;
 
 
-procedure TFPReportCustomBand.SetFont(AValue: TFPReportFont);
-begin
-  if UseParentFont then
-    UseParentFont := False;
-  FFont.Assign(AValue);
-  Changed;
-end;
+{ TFPReportImages }
 
 
-procedure TFPReportCustomBand.SetUseParentFont(AValue: boolean);
+function TFPReportImages.GetImg(AIndex: Integer): TFPReportImageItem;
 begin
 begin
-  if FUseParentFont = AValue then
-    Exit;
-  FUseParentFont := AValue;
-  if FUseParentFont then
-    FreeAndNil(FFont)
-  else
-  begin
-    FFont := TFPReportFont.Create;
-    if Assigned(Owner) then
-      FFont.Assign(TFPReportCustomPage(Owner).Font);
-  end;
-  Changed;
+  Result := Items[AIndex] as TFPReportImageItem;
 end;
 end;
 
 
-procedure TFPReportCustomBand.SetVisibleOnPage(AValue: TFPReportVisibleOnPage);
+function TFPReportImages.GetReportOwner: TFPCustomReport;
 begin
 begin
-  if FVisibleOnPage = AValue then
-    Exit;
-  FVisibleOnPage := AValue;
-  Changed;
+  Result:=Owner as TFPCustomReport;
 end;
 end;
 
 
-function TFPReportCustomBand.GetReportBandName: string;
+
+constructor TFPReportImages.Create(AOwner: TFPCustomReport; AItemClass: TCollectionItemClass);
 begin
 begin
-  Result := 'FPCustomReportBand';
+  inherited Create(aOwner,AItemClass);
 end;
 end;
 
 
-function TFPReportCustomBand.GetData: TFPReportData;
+function TFPReportImages.AddImageItem: TFPReportImageItem;
 begin
 begin
-  result := nil;
+  Result := Add as TFPReportImageItem;
 end;
 end;
 
 
-procedure TFPReportCustomBand.SetDataFromName(AName: String);
+function TFPReportImages.AddFromStream(const AStream: TStream;
+    Handler: TFPCustomImageReaderClass; KeepImage: Boolean): Integer;
+var
+  I: TFPCustomImage;
+  IP: TFPReportImageItem;
+  Reader: TFPCustomImageReader;
 begin
 begin
-  // Do nothing
+  IP := AddImageItem;
+  I := TFPCompactImgRGBA8Bit.Create(0,0);
+  Reader := Handler.Create;
+  try
+    I.LoadFromStream(AStream, Reader);
+  finally
+    Reader.Free;
+  end;
+  IP.Image := I;
+  if Not KeepImage then
+  begin
+    IP.CreateStreamedData;
+    IP.FImage := Nil; // not through property, that would clear the image
+    I.Free;
+  end;
+  Result := Count-1;
 end;
 end;
 
 
-procedure TFPReportCustomBand.SetParent(const AValue: TFPReportElement);
+function TFPReportImages.AddFromFile(const AFileName: string; KeepImage: Boolean): Integer;
+
+  {$IF NOT (FPC_FULLVERSION >= 30101)}
+  function FindReaderFromExtension(extension: String): TFPCustomImageReaderClass;
+  var
+    s: string;
+    r: integer;
+  begin
+    extension := lowercase (extension);
+    if (extension <> '') and (extension[1] = '.') then
+      system.delete (extension,1,1);
+    with ImageHandlers do
+    begin
+      r := count-1;
+      s := extension + ';';
+      while (r >= 0) do
+      begin
+        Result := ImageReader[TypeNames[r]];
+        if (pos(s,{$if (FPC_FULLVERSION = 20604)}Extentions{$else}Extensions{$endif}[TypeNames[r]]+';') <> 0) then
+          Exit;
+        dec (r);
+      end;
+    end;
+    Result := nil;
+  end;
+
+  function FindReaderFromFileName(const filename: String): TFPCustomImageReaderClass;
+  begin
+    Result := FindReaderFromExtension(ExtractFileExt(filename));
+  end;
+  {$ENDIF}
+
+var
+  FS: TFileStream;
 begin
 begin
-  if not ((AValue = nil) or (AValue is TFPReportCustomPage)) then
-    ReportError(SErrNotAReportPage, [AValue.ClassName, AValue.Name]);
-  inherited SetParent(AValue);
+  FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
+  try
+    Result := AddFromStream(FS,
+      {$IF (FPC_FULLVERSION >= 30101)}TFPCustomImage.{$ENDIF}FindReaderFromFileName(AFileName), KeepImage);
+  finally
+    FS.Free;
+  end;
 end;
 end;
 
 
-procedure TFPReportCustomBand.CreateRTLayout;
+function TFPReportImages.AddFromData(const AImageData: Pointer; const AImageDataSize: LongWord): integer;
+var
+  s: TMemoryStream;
 begin
 begin
-  inherited CreateRTLayout;
-  FRTLayout.Left := Page.Layout.Left;
+  s := TMemoryStream.Create;
+  try
+    s.Write(AImageData^, AImageDataSize);
+    s.Position := 0;
+    Result := AddFromStream(s, TFPReaderPNG, True);
+  finally
+    s.Free;
+  end;
 end;
 end;
 
 
-procedure TFPReportCustomBand.PrepareObjects;
-
+function TFPReportImages.GetIndexFromID(const AID: integer): integer;
 var
 var
-  lRTPage: TFPReportCustomPage;
   i: integer;
   i: integer;
-  m: TFPReportMemo;
-  cb: TFPReportCheckbox;
-  img: TFPReportCustomImage;
-  s: string;
-  c: integer;
-  n: TFPExprNode;
-  nIdx: integer;
-
 begin
 begin
-  i := Page.Report.FRTCurPageIdx;
-  if Assigned(Page.Report.RTObjects[i]) then
+  result := -1;
+  if AID<0 then
+    exit;
+  for i := 0 to Count-1 do
   begin
   begin
-    lRTPage := TFPReportCustomPage(Page.Report.RTObjects[i]);
-    Page.Report.FRTCurBand := TFPReportBandClass(ClassType).Create(lRTPage);
-    Page.Report.FRTCurBand.Assign(self);
-    Page.Report.FRTCurBand.CreateRTLayout;
+    if Images[i].ID = AID then
+    begin
+      Result := i;
+      Exit;
+    end;
   end;
   end;
-  inherited PrepareObjects;
+end;
 
 
-  if self is TFPReportGroupHeaderBand then
-    TFPReportGroupHeaderBand(Page.Report.FRTCurBand).CalcGroupConditionValue;
+function TFPReportImages.GetImageFromID(const AID: integer): TFPCustomImage;
 
 
-  if Assigned(FChildren) then
-  begin
-    for c := 0 to Page.Report.FRTCurBand.ChildCount-1 do
-    begin
-      if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomMemo then
-      begin
-        m := TFPReportMemo(Page.Report.FRTCurBand.Child[c]);
-        if moDisableExpressions in m.Options then
-          Continue; // nothing further to do
-        m.ExpandExpressions;
-        // visibility handling
-        if moHideZeros in m.Options then
-        begin
-          if IsStringValueZero(m.Text) then
-          begin
-            m.Visible := False;
-            Continue;
-          end;
-        end;
-        if moSuppressRepeated in m.Options then
-        begin
-          if m.Original.FLastText = m.Text then
-          begin
-            m.Visible := False;
-            Continue;
-          end
-          else
-            m.Original.FLastText := m.Text;
-        end;
-        // aggregate handling
-        for nIdx := 0 to Length(m.Original.ExpressionNodes)-1 do
-        begin
-          n := m.Original.ExpressionNodes[nIdx].ExprNode;
-          if not Assigned(n) then
-            Continue;
-          if n.HasAggregate then
-          begin
-            if moNoResetAggregateOnPrint in m.Options then
-            begin
-              // do nothing
-            end
-                 // apply memo.Options rules if applicable
-            else if ((self is TFPReportCustomPageHeaderBand) and (moResetAggregateOnPage in m.Options))
-                  or ((self is TFPReportCustomColumnHeaderBand) and (moResetAggregateOnColumn in m.Options))
-                  or ((self is TFPReportCustomGroupHeaderBand) and (moResetAggregateOnGroup in m.Options)) then
-                n.InitAggregate
-                 // apply Page/Column/Group/Data footer rule
-            else if (self is TFPReportCustomPageFooterBand)
-                  or (self is TFPReportCustomColumnFooterBand)
-                  or (self is TFPReportCustomGroupFooterBand)
-                  or (self is TFPReportCustomDataFooterBand) then
-                n.InitAggregate
-            else
-              // default rule - reset on print. applies to all memos
-              n.InitAggregate;
-          end;
-        end;
-      end
-      else if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomCheckbox then
-      begin
-        cb := TFPReportCheckbox(Page.Report.FRTCurBand.Child[c]);
-        s := ExpandMacro(cb.Expression, True);
-        cb.FTestResult := StrToBoolDef(s, False);
-      end
-      else if TFPReportElement(Page.Report.FRTCurBand.Child[c]) is TFPReportCustomImage then
-      begin
-        img := TFPReportCustomImage(Page.Report.FRTCurBand.Child[c]);
-        if (img.FieldName <> '') and Assigned(GetData) then
-          img.LoadDBData(GetData);
-      end;
-    end; { for c := 0 to ... }
-  end;  { if Assigned(FChildren) ... }
+Var
+  II : TFPReportImageItem;
+
+begin
+  II:=GetImageItemFromID(AID);
+  if II<>Nil then
+    Result:=II.Image
+  else
+    Result:=Nil;
 end;
 end;
 
 
-procedure TFPReportCustomBand.RecalcLayout;
+function TFPReportImages.GetImageItemFromID(const AID: integer): TFPReportImageItem;
+
+Var
+  I : Integer;
 begin
 begin
-  inherited RecalcLayout;
-  if StretchMode <> smDontStretch then
-    ApplyStretchMode;
+  I:=GetIndexFromID(AID);
+  if I<>-1 then
+    Result:=Images[I]
+  else
+    Result:=Nil;
 end;
 end;
 
 
-procedure TFPReportCustomBand.Assign(Source: TPersistent);
+{ TFPReportPageSize }
+
+procedure TFPReportPageSize.SetHeight(const AValue: TFPReportUnits);
+begin
+  if FHeight = AValue then
+    Exit;
+  FHeight := AValue;
+  Changed;
+end;
+
+procedure TFPReportPageSize.CheckPaperSize;
 var
 var
-  E: TFPReportCustomBand;
+  i: integer;
 begin
 begin
-  inherited Assign(Source);
-  if Source is TFPReportCustomBand then
+  I := PaperManager.IndexOfPaper(FPaperName);
+  if (I <> -1) then
   begin
   begin
-    E := TFPReportCustomBand(Source);
-    FChildBand := E.ChildBand;
-    FStretchMode := E.StretchMode;
-    FVisibleOnPage := E.VisibleOnPage;
-    UseParentFont := E.UseParentFont;
-    if not UseParentFont then
-      Font.Assign(E.Font);
+    FWidth := PaperManager.PaperWidth[I];
+    FHeight := PaperManager.PaperHeight[I];
+    Changed;
   end;
   end;
 end;
 end;
 
 
-class function TFPReportCustomBand.ReportBandType: TFPReportBandType;
+procedure TFPReportPageSize.SetPaperName(const AValue: string);
 begin
 begin
-  Result:=btUnknown;
+  if FPaperName = AValue then
+    Exit;
+  FPaperName := AValue;
+  if (FPaperName <> '') then
+    CheckPaperSize;
 end;
 end;
 
 
-procedure TFPReportCustomBand.BeforePrint;
+procedure TFPReportPageSize.SetWidth(const AValue: TFPReportUnits);
+begin
+  if FWidth = AValue then
+    Exit;
+  FWidth := AValue;
+  Changed;
+end;
+
+procedure TFPReportPageSize.Changed;
+begin
+  if Assigned(FPage) then
+    FPage.PageSizeChanged;
+end;
+
+constructor TFPReportPageSize.Create(APage: TFPReportCustomPage);
+begin
+  FPage := APage;
+end;
+
+procedure TFPReportPageSize.Assign(Source: TPersistent);
 var
 var
-  i: integer;
-  c: TFPReportElement;
+  S: TFPReportPageSize;
 begin
 begin
-  inherited BeforePrint;
-  if Visible = false then
-    exit;
-  if Assigned(FChildren) then
+  if Source is TFPReportPageSize then
   begin
   begin
-    for i := 0 to FChildren.Count-1 do
-    begin
-      c := Child[i];
-      c.BeforePrint;
-    end;
-  end;
+    S := Source as TFPReportPageSize;
+    FPaperName := S.FPaperName;
+    FWidth := S.FWidth;
+    FHeight := S.FHeight;
+    Changed;
+  end
+  else
+    inherited Assign(Source);
 end;
 end;
 
 
-procedure TFPReportCustomBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+{ TFPReportExporter }
+
+procedure TFPReportExporter.SetFPReport(AValue: TFPCustomReport);
 begin
 begin
-  inherited DoWriteLocalProperties(AWriter, AOriginal);
-  AWriter.WriteBoolean('UseParentFont', UseParentFont);
-  if not UseParentFont then
-  begin
-    AWriter.WriteString('FontName', Font.Name);
-    AWriter.WriteInteger('FontSize', Font.Size);
-    AWriter.WriteInteger('FontColor', Font.Color);
-  end;
+  if FPReport = AValue then
+    Exit;
+  if Assigned(FPReport) then
+    FPReport.RemoveFreeNotification(Self);
+  FPReport := AValue;
+  if Assigned(FPReport) then
+    FPReport.FreeNotification(Self);
 end;
 end;
 
 
-constructor TFPReportCustomBand.Create(AOwner: TComponent);
+procedure TFPReportExporter.SetBaseFileName(AValue: string);
 begin
 begin
-  inherited Create(AOwner);
-  FVisibleOnPage := vpAll;
-  FUseParentFont := True;
-  FFont := nil
+  if FBaseFileName=AValue then Exit;
+  FBaseFileName:=AValue;
 end;
 end;
 
 
-destructor TFPReportCustomBand.Destroy;
+procedure TFPReportExporter.Notification(AComponent: TComponent;
+  Operation: TOperation);
 begin
 begin
-  FreeAndNil(FFont);
-  inherited Destroy;
+  inherited Notification(AComponent, Operation);
+  if (Operation=opRemove) and (AComponent=FPReport) then
+    FPReport:=Nil;
 end;
 end;
 
 
-procedure TFPReportCustomBand.WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+procedure TFPReportExporter.RenderImage(aPos: TFPReportRect; var AImage: TFPCustomImage);
 begin
 begin
-  AWriter.PushElement(GetReportBandName);
-  try
-    inherited WriteElement(AWriter, AOriginal);
-    if Assigned(ChildBand) then
-      AWriter.WriteString('ChildBand', ChildBand.Name);
-    if Assigned(GetData) then
-      AWriter.WriteString('Data', GetData.Name);
-    AWriter.WriteString('VisibleOnPage', VisibleOnPageToString(FVisibleOnPage));
-  finally
-    AWriter.PopElement;
+  // Do nothing
+end;
+
+TYpe
+
+  { TMyFPCompactImgRGBA8Bit }
+
+  TMyFPCompactImgRGBA8Bit = Class(TFPCompactImgRGBA8Bit)
+    procedure SetInternalColor (x, y: integer; const Value: TFPColor); override;
   end;
   end;
+
+{ TMyFPCompactImgRGBA8Bit }
+
+procedure TMyFPCompactImgRGBA8Bit.SetInternalColor(x, y: integer; const Value: TFPColor);
+begin
+  if (X<0) or (Y<0) or (X>=Width) or (Y>=Height) then
+    Writeln('(',X,',',Y,') not in (0,0)x(',Width-1,',',Height-1,')')
+  else
+    inherited SetInternalColor(x, y, Value);
 end;
 end;
 
 
-procedure TFPReportCustomBand.ReadElement(AReader: TFPReportStreamer);
-var
-  E: TObject;
-  s: string;
+procedure TFPReportExporter.RenderUnknownElement(aBasePos: TFPReportPoint;
+  AElement: TFPReportElement; ADPI: Integer);
+
+Var
+  C : TFPReportElementExporterCallBack;
+  IC : TFPReportImageRenderCallBack;
+  Img : TFPCustomImage;
+  H,W : Integer;
+  R : TFPReportRect;
+
 begin
 begin
-  E := AReader.FindChild(GetReportBandName);
-  if Assigned(E) then
-  begin
-    AReader.PushElement(E);
-    try
-      inherited ReadElement(AReader);
-      s := AReader.ReadString('ChildBand', '');
-      if s <> '' then
-        Page.Report.AddReference(self.Name, s);
-//        Page.Report.AddReference(self, 'ChildBand', s);
-      FVisibleOnPage := StringToVisibleOnPage(AReader.ReadString('VisibleOnPage', 'vpAll'));
-      FUseParentFont := AReader.ReadBoolean('UseParentFont', UseParentFont);
-      if not FUseParentFont then
+  // Actually, this could be cached using propertyhash...
+  C:=gElementFactory.FindRenderer(TFPReportExporterClass(self.ClassType),TFPReportElementClass(aElement.ClassType));
+  if (C<>Nil) then
+    // There is a direct renderer
+    C(aBasePos, aElement,Self,aDPI)
+  else
+    begin
+    // There is no direct renderer, try rendering to image
+    IC:=gElementFactory.FindImageRenderer(TFPReportElementClass(aElement.ClassType));
+    if Assigned(IC) then
       begin
       begin
-        Font.Name := AReader.ReadString('FontName', Font.Name);
-        Font.Size := AReader.ReadInteger('FontSize', Font.Size);
-        Font.Color := AReader.ReadInteger('FontColor', Font.Color);
+      H := Round(aElement.RTLayout.Height * (aDPI / cMMperInch));
+      W := Round(aElement.RTLayout.Width * (aDPI / cMMperInch));
+      Img:=TFPCompactImgRGBA8Bit.Create(W,H);
+      try
+        IC(aElement,Img);
+        R.Left:=aBasePos.Left+AElement.RTLayout.Left;
+        R.Top:=aBasePos.Top+AElement.RTLayout.Top;
+        R.Width:=AElement.RTLayout.Width;
+        R.Height:=AElement.RTLayout.Height;
+        RenderImage(R,Img);
+      finally
+        Img.Free;
+      end;
       end;
       end;
-
-      // TODO: Read Data information
-      S:=AReader.ReadString('Data','');
-      if (S<>'') then
-        SetDataFromName(S);
-    finally
-      AReader.PopElement;
     end;
     end;
-  end;
 end;
 end;
 
 
-{ TFPReportCustomBandWithData }
 
 
-procedure TFPReportCustomBandWithData.SetData(const AValue: TFPReportData);
+class function TFPReportExporter.DefaultConfig: TFPReportExporterConfigHandler;
 begin
 begin
-  if FData = AValue then
-    Exit;
-  if Assigned(FData) then
-    FData.RemoveFreeNotification(Self);
-  FData := AValue;
-  if Assigned(FData) then
-    FData.FreeNotification(Self);
+  Result:=Nil;
 end;
 end;
 
 
-procedure TFPReportCustomBandWithData.SaveDataToNames;
+procedure TFPReportExporter.Execute;
 begin
 begin
-  inherited SaveDataToNames;
-  if Assigned(FData) then
-    FDataName:=FData.Name
-  else
-    FDataName:='';
+  if (FPReport.RTObjects.Count=0) and AutoRun then
+    FPreport.RunReport;
+  if FPReport.RTObjects.Count > 0 then
+    DoExecute(FPReport.RTObjects);
+end;
+
+procedure TFPReportExporter.SetFileName(const aFileName: String);
+begin
+  // Do nothing
+end;
+
+class procedure TFPReportExporter.RegisterExporter;
+begin
+  ReportExportManager.RegisterExport(Self);
 end;
 end;
 
 
-procedure TFPReportCustomBandWithData.ResolveDataName;
-
+class procedure TFPReportExporter.UnRegisterExporter;
 begin
 begin
-  if (FDataName<>'') then
-    Data:=Report.ReportData.FindReportData(FDataName)
-  else
-    Data:=Nil;
+  ReportExportManager.UnRegisterExport(Self);
 end;
 end;
-procedure TFPReportCustomBandWithData.RestoreDataFromNames;
 
 
+class function TFPReportExporter.Description: String;
 begin
 begin
-  inherited RestoreDataFromNames;
-  ResolveDataName;
+  Result:='';
 end;
 end;
 
 
-function TFPReportCustomBandWithData.GetData: TFPReportData;
+class function TFPReportExporter.Name: String;
 begin
 begin
-  Result := FData;
+  Result:=ClassName;
 end;
 end;
 
 
-procedure TFPReportCustomBandWithData.SetDataFromName(AName: String);
+class function TFPReportExporter.DefaultExtension: String;
 begin
 begin
-  FDataName:=AName;
-  ResolveDataName;
+  Result:='';
 end;
 end;
 
 
-procedure TFPReportCustomBandWithData.Notification(AComponent: TComponent; Operation: TOperation);
+class function TFPReportExporter.MultiFile: Boolean;
 begin
 begin
-  if Operation = opRemove then
-  begin
-    if AComponent = FData then
-      FData := nil;
-  end;
-  inherited Notification(AComponent, Operation);
+  Result:=False;
 end;
 end;
 
 
-constructor TFPReportCustomBandWithData.Create(AOwner: TComponent);
+function TFPReportExporter.ShowConfig: Boolean;
 begin
 begin
-  FData := nil;
-  inherited Create(AOwner);
+  Result:=ReportExportManager.ConfigExporter(Self);
 end;
 end;
 
 
-{ TFPReportCustomGroupFooterBand }
+{ TFPReportPaperSize }
 
 
-procedure TFPReportCustomGroupFooterBand.SetGroupHeader(const AValue: TFPReportCustomGroupHeaderBand);
+constructor TFPReportPaperSize.Create(const AWidth, AHeight: TFPReportUnits);
 begin
 begin
-  if FGroupHeader = AValue then
-    Exit;
-  if Assigned(FGroupHeader) then
-  begin
-    FGroupHeader.FGroupFooter := nil;
-    FGroupHeader.RemoveFreeNotification(Self);
-  end;
-  FGroupHeader := AValue;
-  if Assigned(FGroupHeader) then
-  begin
-    FGroupHeader.FGroupFooter := Self;
-    FGroupHeader.FreeNotification(Self);
-  end;
+  FWidth := AWidth;
+  FHeight := AHeight;
 end;
 end;
 
 
-function TFPReportCustomGroupFooterBand.GetReportBandName: string;
+{ TFPReportFont }
+
+procedure TFPReportFont.SetFontName(const avalue: string);
 begin
 begin
-  Result := 'GroupFooterBand';
+  FFontName := AValue;
 end;
 end;
 
 
-procedure TFPReportCustomGroupFooterBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
+procedure TFPReportFont.SetFontSize(const avalue: integer);
 begin
 begin
-  inherited DoWriteLocalProperties(AWriter, AOriginal);
-  if Assigned(GroupHeader) then
-    AWriter.WriteString('GroupHeader', GroupHeader.Name);
+  FFontSize := AValue;
 end;
 end;
 
 
-procedure TFPReportCustomGroupFooterBand.Notification(AComponent: TComponent; Operation: TOperation);
+procedure TFPReportFont.SetFontColor(const avalue: TFPReportColor);
 begin
 begin
-  if (Operation = opRemove) and (AComponent = FGroupHeader) then
-    FGroupHeader := nil;
-  inherited Notification(AComponent, Operation);
+  FFontColor := AValue;
 end;
 end;
 
 
-procedure TFPReportCustomGroupFooterBand.ReadElement(AReader: TFPReportStreamer);
-var
-  s: string;
-//  c: TFPReportElement;
+constructor TFPReportFont.Create;
 begin
 begin
-//  c := nil;
-  inherited ReadElement(AReader);
-  s := AReader.ReadString('GroupHeader', '');
-  if s = '' then
-    Exit;
-  // TODO: recursively search Page.Report for the GroupHeader
-  //c := Page.Report.FindComponent(s);
-  //if Assigned(c) then
-  //  FGroupHeader := TFPReportCustomGroupHeaderBand(c);
+  inherited Create;
+  FFontName := cDefaultFont;
+  FFontColor := clBlack;
+  FFontSize := 10;
 end;
 end;
 
 
-class function TFPReportCustomGroupFooterBand.ReportBandType: TFPReportBandType;
+procedure TFPReportFont.Assign(Source: TPersistent);
+var
+  o: TFPReportFont;
 begin
 begin
-  Result:=btGroupFooter;
+  //inherited Assign(Source);
+  if (Source = nil) or not (Source is TFPReportFont) then
+    ReportError(SErrCantAssignReportFont);
+  o := TFPReportFont(Source);
+  FFontName := o.Name;
+  FFontSize := o.Size;
+  FFontColor := o.Color;
 end;
 end;
 
 
+{ TFPReportPaperManager }
 
 
-{ TFPReportImageItem }
+function TFPReportPaperManager.GetPaperHeight(AIndex: integer): TFPReportUnits;
+begin
+  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Height;
+end;
 
 
-function TFPReportImageItem.GetHeight: Integer;
+function TFPReportPaperManager.GetPaperHeightByName(AName: string): TFPReportUnits;
 begin
 begin
-  If Assigned(FImage) then
-    Result:=FImage.Height
-  else
-    Result:=FHeight;
+  Result := GetPaperByName(AName).Height;
 end;
 end;
 
 
-function TFPReportImageItem.GetStreamed: TBytes;
+function TFPReportPaperManager.GetPaperCount: integer;
 begin
 begin
-  if Length(FStreamed)=0 then
-    CreateStreamedData;
-  Result:=FStreamed;
+  Result := FPaperSizes.Count;
 end;
 end;
 
 
-function TFPReportImageItem.GetWidth: Integer;
+function TFPReportPaperManager.GetPaperName(AIndex: integer): string;
 begin
 begin
-  If Assigned(FImage) then
-    Result:=FImage.Width
-  else
-    Result:=FWidth;
+  Result := FPaperSizes[AIndex];
 end;
 end;
 
 
-procedure TFPReportImageItem.SetImage(AValue: TFPCustomImage);
+function TFPReportPaperManager.GetPaperWidth(AIndex: integer): TFPReportUnits;
 begin
 begin
-  if FImage=AValue then Exit;
-  FImage:=AValue;
-  SetLength(FStreamed,0);
+  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Width;
 end;
 end;
 
 
-procedure TFPReportImageItem.SetStreamed(AValue: TBytes);
+function TFPReportPaperManager.GetPaperWidthByName(AName: string): TFPReportUnits;
 begin
 begin
-  If AValue=FStreamed then exit;
-  SetLength(FStreamed,0);
-  FStreamed:=AValue;
+  Result := GetPaperByName(AName).Width;
 end;
 end;
 
 
-procedure TFPReportImageItem.LoadPNGFromStream(AStream: TStream);
+function TFPReportPaperManager.FindPaper(const AName: string): TFPReportPaperSize;
 var
 var
-  PNGReader: TFPReaderPNG;
+  I: integer;
 begin
 begin
-  if not Assigned(AStream) then
-    Exit;
-
-  { we use Image property here so it frees any previous image }
-  if Assigned(FImage) then
-    FreeAndNil(FImage);
-  FImage := TFPCompactImgRGBA8Bit.Create(0, 0);
-  try
-    PNGReader := TFPReaderPNG.Create;
-    try
-      FImage.LoadFromStream(AStream, PNGReader); // auto size image
-    finally
-      PNGReader.Free;
-    end;
-  except
-    FreeAndNil(FImage);
-  end;
+  I := IndexOfPaper(AName);
+  if (I = -1) then
+    Result := nil
+  else
+    Result := TFPReportPaperSize(FPaperSizes.Objects[i]);
 end;
 end;
 
 
-constructor TFPReportImageItem.Create(ACollection: TCollection);
+function TFPReportPaperManager.GetPaperByname(const AName: string): TFPReportPaperSize;
 begin
 begin
-  inherited Create(ACollection);
-  FOwnsImage := True;
+  Result := FindPaper(AName);
+  if Result = nil then
+    ReportError(SErrUnknownPaper, [AName]);
 end;
 end;
 
 
-destructor TFPReportImageItem.Destroy;
+constructor TFPReportPaperManager.Create(AOwner: TComponent);
 begin
 begin
-  if FOwnsImage then
-    FreeAndNil(FImage);
-  inherited Destroy;
+  inherited Create(AOwner);
+  FPaperSizes := TStringList.Create;
+  FPaperSizes.Sorted := True;
 end;
 end;
 
 
-procedure TFPReportImageItem.CreateStreamedData;
-Var
-  X, Y: Integer;
-  C: TFPColor;
-  MS: TMemoryStream;
-  Str: TStream;
-  CWhite: TFPColor; // white color
+destructor TFPReportPaperManager.Destroy;
+var
+  I: integer;
 begin
 begin
-  FillMem(@CWhite, SizeOf(CWhite), $FF);
-  FWidth:=Image.Width;
-  FHeight:=Image.Height;
-  Str := nil;
-  MS := TMemoryStream.Create;
-  try
-    Str := MS;
-    for Y:=0 to FHeight-1 do
-      for X:=0 to FWidth-1 do
-        begin
-        C:=Image.Colors[x,y];
-        if C.alpha < $FFFF then // remove alpha channel - assume white background
-          C := AlphaBlend(CWhite, C);
-
-        Str.WriteByte(C.Red shr 8);
-        Str.WriteByte(C.Green shr 8);
-        Str.WriteByte(C.blue shr 8);
-        end;
-    if Str<>MS then
-      Str.Free;
-    Str := nil;
-    SetLength(FStreamed, MS.Size);
-    MS.Position := 0;
-    if MS.Size>0 then
-      MS.ReadBuffer(FStreamed[0], MS.Size);
-  finally
-    Str.Free;
-    MS.Free;
+  if Assigned(FPaperSizes) then
+  begin
+    for I := 0 to FPaperSizes.Count - 1 do
+      FPaperSizes.Objects[i].Free;
+    FreeAndNil(FPaperSizes);
   end;
   end;
+  inherited Destroy;
 end;
 end;
 
 
-function TFPReportImageItem.WriteImageStream(AStream: TStream): UInt64;
+procedure TFPReportPaperManager.Clear;
 var
 var
-  Img: TBytes;
+  i: integer;
 begin
 begin
-  Img := StreamedData;
-  Result := Length(Img);
-  AStream.WriteBuffer(Img[0],Result);
+  for i := 0 to FPaperSizes.Count-1 do
+    if Assigned(FPaperSizes.Objects[i]) then
+      FPaperSizes.Objects[i].Free;
+  FPaperSizes.Clear;
 end;
 end;
 
 
-function TFPReportImageItem.Equals(AImage: TFPCustomImage): boolean;
-var
-  x, y: Integer;
+function TFPReportPaperManager.IndexOfPaper(const AName: string): integer;
 begin
 begin
-  Result := True;
-  for x := 0 to Image.Width-1 do
-    for y := 0 to Image.Height-1 do
-      if Image.Pixels[x, y] <> AImage.Pixels[x, y] then
-      begin
-        Result := False;
-        Exit;
-      end;
+  if not Assigned(FPaperSizes) then
+    Result := -1
+  else
+    Result := FPaperSizes.IndexOf(AName);
 end;
 end;
 
 
-procedure TFPReportImageItem.WriteElement(AWriter: TFPReportStreamer);
+procedure TFPReportPaperManager.RegisterPaper(const AName: string; const AWidth, AHeight: TFPReportUnits);
 var
 var
-  ms: TMemoryStream;
-  png: TFPWriterPNG;
+  I: integer;
+  S: TFPReportPaperSize;
 begin
 begin
-  if Assigned(Image) then
+  I := FPaperSizes.IndexOf(AName);
+  if (I = -1) then
   begin
   begin
-    ms := TMemoryStream.Create;
-    try
-      png := TFPWriterPNG.create;
-      png.Indexed := False;
-      Image.SaveToStream(ms, png);
-      ms.Position := 0;
-      AWriter.WriteStream('ImageData', ms);
-    finally
-      png.Free;
-      ms.Free;
-    end;
-  end;
+    S := TFPReportPaperSize.Create(AWidth, AHeight);
+    FPaperSizes.AddObject(AName, S);
+  end
+  else
+    ReportError(SErrDuplicatePaperName, [AName]);
 end;
 end;
 
 
-procedure TFPReportImageItem.ReadElement(AReader: TFPReportStreamer);
-var
-  ms: TMemoryStream;
+{ Got details from Wikipedia [https://simple.wikipedia.org/wiki/Paper_size] }
+procedure TFPReportPaperManager.RegisterStandardSizes;
 begin
 begin
-  ms := TMemoryStream.Create;
-  try
-    if AReader.ReadStream('ImageData', ms) then
-    begin
-      ms.Position := 0;
-      LoadPNGFromStream(ms);
-    end;
-  finally
-    ms.Free;
-  end;
+  // As per TFPReportUnits, size is specified in millimetres.
+  RegisterPaper('A3', 297, 420);
+  RegisterPaper('A4', 210, 297);
+  RegisterPaper('A5', 148, 210);
+  RegisterPaper('Letter', 216, 279);
+  RegisterPaper('Legal', 216, 356);
+  RegisterPaper('Ledger', 279, 432);
+  RegisterPaper('DL',	220, 110);
+  RegisterPaper('B5',	176, 250);
+  RegisterPaper('C5',	162, 229);
 end;
 end;
 
 
-{ TFPReportImages }
-
-function TFPReportImages.GetImg(AIndex: Integer): TFPReportImageItem;
+procedure TFPReportPaperManager.GetRegisteredSizes(var AList: TStringList);
+var
+  i: integer;
 begin
 begin
-  Result := Items[AIndex] as TFPReportImageItem;
+  if not Assigned(AList) then
+    Exit;
+  AList.Clear;
+  for i := 0 to FPaperSizes.Count - 1 do
+    AList.Add(PaperNames[i]);
 end;
 end;
 
 
-function TFPReportImages.GetReportOwner: TFPCustomReport;
+procedure DoneReporting;
 begin
 begin
-  Result:=Owner as TFPCustomReport;
+  if Assigned(uPaperManager) then
+    FreeAndNil(uPaperManager);
+  TFPReportCustomCheckbox.ImgFalse.Free;
+  TFPReportCustomCheckbox.ImgTrue.Free;
 end;
 end;
 
 
+{ TFPTextBlockList }
 
 
-constructor TFPReportImages.Create(AOwner: TFPCustomReport; AItemClass: TCollectionItemClass);
+function TFPTextBlockList.GetItem(AIndex: Integer): TFPTextBlock;
 begin
 begin
-  inherited Create(aOwner,AItemClass);
+  Result := TFPTextBlock(inherited GetItem(AIndex));
 end;
 end;
 
 
-function TFPReportImages.AddImageItem: TFPReportImageItem;
+procedure TFPTextBlockList.SetItem(AIndex: Integer; AObject: TFPTextBlock);
 begin
 begin
-  Result := Add as TFPReportImageItem;
+  inherited SetItem(AIndex, AObject);
 end;
 end;
 
 
-function TFPReportImages.AddFromStream(const AStream: TStream;
-    Handler: TFPCustomImageReaderClass; KeepImage: Boolean): Integer;
-var
-  I: TFPCustomImage;
-  IP: TFPReportImageItem;
-  Reader: TFPCustomImageReader;
+{ TFPReportDataField }
+
+function TFPReportDataField.GetValue: variant;
 begin
 begin
-  IP := AddImageItem;
-  I := TFPCompactImgRGBA8Bit.Create(0,0);
-  Reader := Handler.Create;
-  try
-    I.LoadFromStream(AStream, Reader);
-  finally
-    Reader.Free;
-  end;
-  IP.Image := I;
-  if Not KeepImage then
-  begin
-    IP.CreateStreamedData;
-    IP.FImage := Nil; // not through property, that would clear the image
-    I.Free;
-  end;
-  Result := Count-1;
+  Result := Null;
+  if Assigned(Collection) then
+    TFPReportDatafields(Collection).ReportData.DoGetValue(FieldName, Result);
 end;
 end;
 
 
-function TFPReportImages.AddFromFile(const AFileName: string; KeepImage: Boolean): Integer;
-
-  {$IF NOT (FPC_FULLVERSION >= 30101)}
-  function FindReaderFromExtension(extension: String): TFPCustomImageReaderClass;
-  var
-    s: string;
-    r: integer;
+procedure TFPReportDataField.Assign(Source: TPersistent);
+var
+  F: TFPReportDataField;
+begin
+  if Source is TFPReportDataField then
   begin
   begin
-    extension := lowercase (extension);
-    if (extension <> '') and (extension[1] = '.') then
-      system.delete (extension,1,1);
-    with ImageHandlers do
-    begin
-      r := count-1;
-      s := extension + ';';
-      while (r >= 0) do
-      begin
-        Result := ImageReader[TypeNames[r]];
-        if (pos(s,{$if (FPC_FULLVERSION = 20604)}Extentions{$else}Extensions{$endif}[TypeNames[r]]+';') <> 0) then
-          Exit;
-        dec (r);
-      end;
-    end;
-    Result := nil;
-  end;
+    F := Source as TFPReportDataField;
+    FDisplayWidth := F.FDisplayWidth;
+    FFieldKind := F.FFieldKind;
+    FFieldName := F.FFieldName;
+  end
+  else
+    inherited Assign(Source);
+end;
 
 
-  function FindReaderFromFileName(const filename: String): TFPCustomImageReaderClass;
-  begin
-    Result := FindReaderFromExtension(ExtractFileExt(filename));
-  end;
-  {$ENDIF}
+{ TFPReportDataFields }
 
 
-var
-  FS: TFileStream;
+function TFPReportDataFields.GetF(AIndex: integer): TFPReportDataField;
 begin
 begin
-  FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
-  try
-    Result := AddFromStream(FS,
-      {$IF (FPC_FULLVERSION >= 30101)}TFPCustomImage.{$ENDIF}FindReaderFromFileName(AFileName), KeepImage);
-  finally
-    FS.Free;
-  end;
+  Result := TFPReportDataField(Items[AIndex]);
 end;
 end;
 
 
-function TFPReportImages.AddFromData(const AImageData: Pointer; const AImageDataSize: LongWord): integer;
-var
-  s: TMemoryStream;
+procedure TFPReportDataFields.SetF(AIndex: integer; const AValue: TFPReportDataField);
 begin
 begin
-  s := TMemoryStream.Create;
-  try
-    s.Write(AImageData^, AImageDataSize);
-    s.Position := 0;
-    Result := AddFromStream(s, TFPReaderPNG, True);
-  finally
-    s.Free;
-  end;
+  Items[AIndex] := AValue;
 end;
 end;
 
 
-function TFPReportImages.GetIndexFromID(const AID: integer): integer;
-var
-  i: integer;
+function TFPReportDataFields.AddField(AFieldName: string; AFieldKind: TFPReportFieldKind): TFPReportDataField;
 begin
 begin
-  result := -1;
-  if AID<0 then
-    exit;
-  for i := 0 to Count-1 do
-  begin
-    if Images[i].ID = AID then
-    begin
-      Result := i;
-      Exit;
-    end;
+  Result := Add as TFPReportDataField;
+  try
+    Result.FieldName := AFieldName;
+    Result.FieldKind := AFieldKind;
+  except
+    Result.Free;
+    raise;
   end;
   end;
 end;
 end;
 
 
-function TFPReportImages.GetImageFromID(const AID: integer): TFPCustomImage;
-
-Var
-  II : TFPReportImageItem;
-
+function TFPReportDataFields.IndexOfField(const AFieldName: string): integer;
 begin
 begin
-  II:=GetImageItemFromID(AID);
-  if II<>Nil then
-    Result:=II.Image
-  else
-    Result:=Nil;
+  Result := Count - 1;
+  while (Result >= 0) and (CompareText(AFieldName, GetF(Result).FieldName) <> 0) do
+    Dec(Result);
 end;
 end;
 
 
-function TFPReportImages.GetImageItemFromID(const AID: integer): TFPReportImageItem;
-
-Var
-  I : Integer;
+function TFPReportDataFields.FindField(const AFieldName: string): TFPReportDataField;
+var
+  I: integer;
 begin
 begin
-  I:=GetIndexFromID(AID);
-  if I<>-1 then
-    Result:=Images[I]
+  I := IndexOfField(AFieldName);
+  if (I = -1) then
+    Result := nil
   else
   else
-    Result:=Nil;
+    Result := GetF(I);
 end;
 end;
 
 
-{ TFPReportPageSize }
-
-procedure TFPReportPageSize.SetHeight(const AValue: TFPReportUnits);
+function TFPReportDataFields.FindField(const AFieldName: string; const AFieldKind: TFPReportFieldKind): TFPReportDataField;
+var
+  lIndex: integer;
 begin
 begin
-  if FHeight = AValue then
-    Exit;
-  FHeight := AValue;
-  Changed;
+  lIndex := Count - 1;
+  while (lIndex >= 0) and (not SameText(AFieldName, GetF(lIndex).FieldName)) and (GetF(lIndex).FieldKind <> AFieldKind) do
+      Dec(lIndex);
+
+  if (lIndex = -1) then
+    Result := nil
+  else
+    Result := GetF(lIndex);
 end;
 end;
 
 
-procedure TFPReportPageSize.CheckPaperSize;
-var
-  i: integer;
+function TFPReportDataFields.FieldByName(const AFieldName: string): TFPReportDataField;
 begin
 begin
-  I := PaperManager.IndexOfPaper(FPaperName);
-  if (I <> -1) then
+  Result := FindField(AFieldName);
+  if (Result = nil) then
   begin
   begin
-    FWidth := PaperManager.PaperWidth[I];
-    FHeight := PaperManager.PaperHeight[I];
-    Changed;
+    if Assigned(ReportData) then
+      ReportError(SErrUnknownField, [ReportData.Name, AFieldName])
+    else
+      ReportError(SErrUnknownField, ['', AFieldName]);
   end;
   end;
 end;
 end;
 
 
-procedure TFPReportPageSize.SetPaperName(const AValue: string);
+{ TFPReportData }
+
+procedure TFPReportData.SetDataFields(const AValue: TFPReportDataFields);
 begin
 begin
-  if FPaperName = AValue then
+  if (FDataFields = AValue) then
     Exit;
     Exit;
-  FPaperName := AValue;
-  if (FPaperName <> '') then
-    CheckPaperSize;
+  FDataFields.Assign(AValue);
 end;
 end;
 
 
-procedure TFPReportPageSize.SetWidth(const AValue: TFPReportUnits);
+function TFPReportData.GetFieldCount: integer;
 begin
 begin
-  if FWidth = AValue then
-    Exit;
-  FWidth := AValue;
-  Changed;
+  Result := FDatafields.Count;
 end;
 end;
 
 
-procedure TFPReportPageSize.Changed;
+function TFPReportData.GetFieldName(Index: integer): string;
 begin
 begin
-  if Assigned(FPage) then
-    FPage.PageSizeChanged;
+  Result := FDatafields[Index].FieldName;
 end;
 end;
 
 
-constructor TFPReportPageSize.Create(APage: TFPReportCustomPage);
+function TFPReportData.GetFieldType(AFieldName: string): TFPReportFieldKind;
 begin
 begin
-  FPage := APage;
+  Result := FDatafields.FieldByName(AFieldName).FieldKind;
 end;
 end;
 
 
-procedure TFPReportPageSize.Assign(Source: TPersistent);
-var
-  S: TFPReportPageSize;
+function TFPReportData.GetFieldValue(AFieldName: string): variant;
 begin
 begin
-  if Source is TFPReportPageSize then
-  begin
-    S := Source as TFPReportPageSize;
-    FPaperName := S.FPaperName;
-    FWidth := S.FWidth;
-    FHeight := S.FHeight;
-    Changed;
-  end
-  else
-    inherited Assign(Source);
+  Result := varNull;
+  DoGetValue(AFieldName, Result);
 end;
 end;
 
 
-{ TFPReportExporter }
+function TFPReportData.GetFieldWidth(AFieldName: string): integer;
+begin
+  Result := FDataFields.FieldByName(AFieldName).DisplayWidth;
+end;
 
 
-procedure TFPReportExporter.SetFPReport(AValue: TFPCustomReport);
+function TFPReportData.CreateDataFields: TFPReportDataFields;
 begin
 begin
-  if FPReport = AValue then
-    Exit;
-  if Assigned(FPReport) then
-    FPReport.RemoveFreeNotification(Self);
-  FPReport := AValue;
-  if Assigned(FPReport) then
-    FPReport.FreeNotification(Self);
+  Result := TFPReportDataFields.Create(TFPReportDataField);
 end;
 end;
 
 
-procedure TFPReportExporter.SetBaseFileName(AValue: string);
+procedure TFPReportData.DoGetValue(const AFieldName: string; var AValue: variant);
 begin
 begin
-  if FBaseFileName=AValue then Exit;
-  FBaseFileName:=AValue;
+  AValue := Null;
 end;
 end;
 
 
-procedure TFPReportExporter.Notification(AComponent: TComponent;
-  Operation: TOperation);
+procedure TFPReportData.DoInitDataFields;
 begin
 begin
-  inherited Notification(AComponent, Operation);
-  if (Operation=opRemove) and (AComponent=FPReport) then
-    FPReport:=Nil;
+  // Do nothing.
 end;
 end;
 
 
-procedure TFPReportExporter.RenderImage(aPos: TFPReportRect; var AImage: TFPCustomImage);
+procedure TFPReportData.DoOpen;
 begin
 begin
   // Do nothing
   // Do nothing
 end;
 end;
 
 
-TYpe
-
-  { TMyFPCompactImgRGBA8Bit }
-
-  TMyFPCompactImgRGBA8Bit = Class(TFPCompactImgRGBA8Bit)
-    procedure SetInternalColor (x, y: integer; const Value: TFPColor); override;
-  end;
-
-{ TMyFPCompactImgRGBA8Bit }
-
-procedure TMyFPCompactImgRGBA8Bit.SetInternalColor(x, y: integer; const Value: TFPColor);
+procedure TFPReportData.DoFirst;
 begin
 begin
-  if (X<0) or (Y<0) or (X>=Width) or (Y>=Height) then
-    Writeln('(',X,',',Y,') not in (0,0)x(',Width-1,',',Height-1,')')
-  else
-    inherited SetInternalColor(x, y, Value);
+  // Do nothing
 end;
 end;
 
 
-procedure TFPReportExporter.RenderUnknownElement(aBasePos: TFPReportPoint;
-  AElement: TFPReportElement; ADPI: Integer);
-
-Var
-  C : TFPReportElementExporterCallBack;
-  IC : TFPReportImageRenderCallBack;
-  Img : TFPCustomImage;
-  H,W : Integer;
-  R : TFPReportRect;
-
+procedure TFPReportData.DoNext;
 begin
 begin
-  // Actually, this could be cached using propertyhash...
-  C:=gElementFactory.FindRenderer(TFPReportExporterClass(self.ClassType),TFPReportElementClass(aElement.ClassType));
-  if (C<>Nil) then
-    // There is a direct renderer
-    C(aBasePos, aElement,Self,aDPI)
-  else
-    begin
-    // There is no direct renderer, try rendering to image
-    IC:=gElementFactory.FindImageRenderer(TFPReportElementClass(aElement.ClassType));
-    if Assigned(IC) then
-      begin
-      H := Round(aElement.RTLayout.Height * (aDPI / cMMperInch));
-      W := Round(aElement.RTLayout.Width * (aDPI / cMMperInch));
-      Img:=TFPCompactImgRGBA8Bit.Create(W,H);
-      try
-        IC(aElement,Img);
-        R.Left:=aBasePos.Left+AElement.RTLayout.Left;
-        R.Top:=aBasePos.Top+AElement.RTLayout.Top;
-        R.Width:=AElement.RTLayout.Width;
-        R.Height:=AElement.RTLayout.Height;
-        RenderImage(R,Img);
-      finally
-        Img.Free;
-      end;
-      end;
-    end;
+  // Do nothing
 end;
 end;
 
 
-
-class function TFPReportExporter.DefaultConfig: TFPReportExporterConfigHandler;
+procedure TFPReportData.DoClose;
 begin
 begin
-  Result:=Nil;
+  // Do nothing
 end;
 end;
 
 
-procedure TFPReportExporter.Execute;
+function TFPReportData.DoEOF: boolean;
 begin
 begin
-  if (FPReport.RTObjects.Count=0) and AutoRun then
-    FPreport.RunReport;
-  if FPReport.RTObjects.Count > 0 then
-    DoExecute(FPReport.RTObjects);
+  Result := False;
 end;
 end;
 
 
-procedure TFPReportExporter.SetFileName(const aFileName: String);
+constructor TFPReportData.Create(AOwner: TComponent);
 begin
 begin
-  // Do nothing
+  inherited Create(AOwner);
+  FDatafields := CreateDataFields;
+  FDatafields.FReportData := Self;
 end;
 end;
 
 
-class procedure TFPReportExporter.RegisterExporter;
+destructor TFPReportData.Destroy;
 begin
 begin
-  ReportExportManager.RegisterExport(Self);
+  FreeAndNil(FDatafields);
+  inherited Destroy;
 end;
 end;
 
 
-class procedure TFPReportExporter.UnRegisterExporter;
+procedure TFPReportData.InitFieldDefs;
 begin
 begin
-  ReportExportManager.UnRegisterExport(Self);
+  if FIsOpened then
+    ReportError(SErrInitFieldsNotAllowedAfterOpen);
+  DoInitDataFields;
 end;
 end;
 
 
-class function TFPReportExporter.Description: String;
+procedure TFPReportData.Open;
 begin
 begin
-  Result:='';
+  if Assigned(FOnOpen) then
+    FOnOpen(Self);
+  DoOpen;
+  InitFieldDefs;
+  FIsOpened := True;
+  FRecNo := 1;
 end;
 end;
 
 
-class function TFPReportExporter.Name: String;
+procedure TFPReportData.First;
 begin
 begin
-  Result:=ClassName;
+  if Assigned(FOnFirst) then
+    FOnFirst(Self);
+  DoFirst;
+  FRecNo := 1;
 end;
 end;
 
 
-class function TFPReportExporter.DefaultExtension: String;
+procedure TFPReportData.Next;
 begin
 begin
-  Result:='';
+  Inc(FRecNo);
+  if Assigned(FOnNext) then
+    FOnNext(Self);
+  DoNext;
 end;
 end;
 
 
-class function TFPReportExporter.MultiFile: Boolean;
+procedure TFPReportData.Close;
 begin
 begin
-  Result:=False;
+  if Assigned(FOnClose) then
+    FOnClose(Self);
+  DoClose;
+  FIsOpened := False;
+  FRecNo := -1;
 end;
 end;
 
 
-function TFPReportExporter.ShowConfig: Boolean;
+function TFPReportData.EOF: boolean;
 begin
 begin
-  Result:=ReportExportManager.ConfigExporter(Self);
+  Result := False;
+  if Assigned(FOnGetEOF) then
+    FOnGetEOF(Self, Result);
+  if not Result then
+    Result := DoEOF;
 end;
 end;
 
 
-{ TFPReportPaperSize }
-
-constructor TFPReportPaperSize.Create(const AWidth, AHeight: TFPReportUnits);
+procedure TFPReportData.GetFieldList(List: TStrings);
+var
+  I: integer;
 begin
 begin
-  FWidth := AWidth;
-  FHeight := AHeight;
+  List.BeginUpdate;
+  try
+    List.Clear;
+    for I := 0 to FDataFields.Count - 1 do
+      List.add(FDataFields[I].FieldName);
+  finally
+    List.EndUpdate;
+  end;
 end;
 end;
 
 
-{ TFPReportFont }
-
-procedure TFPReportFont.SetFontName(const avalue: string);
+function TFPReportData.IndexOfField(const AFieldName: string): Integer;
 begin
 begin
-  FFontName := AValue;
+  Result:=  FDataFields.IndexOfField(AFieldName);
 end;
 end;
 
 
-procedure TFPReportFont.SetFontSize(const avalue: integer);
+function TFPReportData.HasField(const AFieldName: string): boolean;
 begin
 begin
-  FFontSize := AValue;
+  Result := FDataFields.IndexOfField(AFieldName) <> -1;
 end;
 end;
 
 
-procedure TFPReportFont.SetFontColor(const avalue: TFPReportColor);
+
+{ TFPReportClassMapping }
+
+function TFPReportClassMapping.IndexOfExportRenderer(
+  AClass: TFPReportExporterClass): Integer;
 begin
 begin
-  FFontColor := AValue;
+  Result:=Length(FRenderers)-1;
+  While (Result>=0) and (FRenderers[Result].aClass<>AClass) do
+    Dec(Result);
 end;
 end;
 
 
-constructor TFPReportFont.Create;
+constructor TFPReportClassMapping.Create(const AMappingName: string; AElementClass: TFPReportElementClass);
 begin
 begin
-  inherited Create;
-  FFontName := cDefaultFont;
-  FFontColor := clBlack;
-  FFontSize := 10;
+  FMappingName :=  AMappingName;
+  FReportElementClass := AElementClass;
 end;
 end;
 
 
-procedure TFPReportFont.Assign(Source: TPersistent);
-var
-  o: TFPReportFont;
+function TFPReportClassMapping.AddRenderer(aExporterClass: TFPReportExporterClass; aCallback: TFPReportElementExporterCallBack ): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
+
 begin
 begin
-  //inherited Assign(Source);
-  if (Source = nil) or not (Source is TFPReportFont) then
-    ReportError(SErrCantAssignReportFont);
-  o := TFPReportFont(Source);
-  FFontName := o.Name;
-  FFontSize := o.Size;
-  FFontColor := o.Color;
+  Result:=nil;
+  I:=IndexOfExportRenderer(aExporterClass);
+  if (I=-1) then
+    begin
+    I:=Length(FRenderers);
+    SetLength(FRenderers,I+1);
+    FRenderers[i].aClass:=aExporterClass;
+    FRenderers[i].aCallback:=Nil;
+    end;
+  Result:=FRenderers[i].aCallback;
+  FRenderers[i].aCallback:=aCallback;
 end;
 end;
 
 
-{ TFPReportPaperManager }
+function TFPReportClassMapping.FindRenderer(aClass: TFPReportExporterClass): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
 
 
-function TFPReportPaperManager.GetPaperHeight(AIndex: integer): TFPReportUnits;
 begin
 begin
-  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Height;
+  I:=IndexOfExportRenderer(aClass);
+  if I<>-1 then
+    Result:=FRenderers[I].aCallback
+  else
+    Result:=Nil;
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperHeightByName(AName: string): TFPReportUnits;
+{ TFPReportElementFactory }
+
+function TFPReportElementFactory.GetM(Aindex : integer): TFPReportClassMapping;
 begin
 begin
-  Result := GetPaperByName(AName).Height;
+  Result:=TFPReportClassMapping(FList[AIndex]);
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperCount: integer;
+function TFPReportElementFactory.IndexOfElementName(const AElementName: string): Integer;
+
 begin
 begin
-  Result := FPaperSizes.Count;
+  Result:=Flist.Count-1;
+  While (Result>=0) and not SameText(Mappings[Result].MappingName, AElementName) do
+    Dec(Result);
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperName(AIndex: integer): string;
+function TFPReportElementFactory.IndexOfElementClass(const AElementClass: TFPReportElementClass): Integer;
+
 begin
 begin
-  Result := FPaperSizes[AIndex];
+  Result:=Flist.Count-1;
+  While (Result>=0) and (Mappings[Result].ReportElementClass<>AElementClass) do
+    Dec(Result);
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperWidth(AIndex: integer): TFPReportUnits;
+constructor TFPReportElementFactory.Create;
 begin
 begin
-  Result := TFPReportPaperSize(FPaperSizes.Objects[AIndex]).Width;
+  FList := TFPObjectList.Create;
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperWidthByName(AName: string): TFPReportUnits;
+destructor TFPReportElementFactory.Destroy;
 begin
 begin
-  Result := GetPaperByName(AName).Width;
+  FList.Free;
+  inherited Destroy;
 end;
 end;
 
 
-function TFPReportPaperManager.FindPaper(const AName: string): TFPReportPaperSize;
-var
-  I: integer;
+function TFPReportElementFactory.FindRenderer(aClass: TFPReportExporterClass;
+  AElement: TFPReportElementClass): TFPReportElementExporterCallBack;
+
+Var
+  I : Integer;
+
 begin
 begin
-  I := IndexOfPaper(AName);
-  if (I = -1) then
-    Result := nil
-  else
-    Result := TFPReportPaperSize(FPaperSizes.Objects[i]);
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    Result:=Mappings[i].FindRenderer(aClass);
 end;
 end;
 
 
-function TFPReportPaperManager.GetPaperByname(const AName: string): TFPReportPaperSize;
+function TFPReportElementFactory.FindImageRenderer(
+  AElement: TFPReportElementClass): TFPReportImageRenderCallBack;
+Var
+  I : Integer;
+
 begin
 begin
-  Result := FindPaper(AName);
-  if Result = nil then
-    ReportError(SErrUnknownPaper, [AName]);
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    Result:=Mappings[i].ImageRenderCallback;
 end;
 end;
 
 
-constructor TFPReportPaperManager.Create(AOwner: TComponent);
+function TFPReportElementFactory.RegisterImageRenderer(AElement: TFPReportElementClass; ARenderer: TFPReportImageRenderCallBack
+  ): TFPReportImageRenderCallBack;
+Var
+  I : Integer;
 begin
 begin
-  inherited Create(AOwner);
-  FPaperSizes := TStringList.Create;
-  FPaperSizes.Sorted := True;
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if I<>-1 then
+    begin
+    Result:=Mappings[i].ImageRenderCallback;
+    Mappings[i].ImageRenderCallback:=ARenderer;
+    end;
 end;
 end;
 
 
-destructor TFPReportPaperManager.Destroy;
-var
-  I: integer;
+function TFPReportElementFactory.RegisterElementRenderer(AElement: TFPReportElementClass; ARenderClass: TFPReportExporterClass;
+  ARenderer: TFPReportElementExporterCallBack): TFPReportElementExporterCallBack;
+Var
+  I : Integer;
 begin
 begin
-  if Assigned(FPaperSizes) then
-  begin
-    for I := 0 to FPaperSizes.Count - 1 do
-      FPaperSizes.Objects[i].Free;
-    FreeAndNil(FPaperSizes);
-  end;
-  inherited Destroy;
+  Result:=nil;
+  I:=IndexOfElementClass(aElement);
+  if (I<>-1) then
+    Result:=Mappings[i].AddRenderer(aRenderClass,ARenderer);
 end;
 end;
 
 
-procedure TFPReportPaperManager.Clear;
-var
-  i: integer;
+procedure TFPReportElementFactory.RegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
 begin
 begin
-  for i := 0 to FPaperSizes.Count-1 do
-    if Assigned(FPaperSizes.Objects[i]) then
-      FPaperSizes.Objects[i].Free;
-  FPaperSizes.Clear;
+  I:=IndexOfElementName(aElementName);
+  if I<>-1 then
+    Mappings[i].EditorClass:=AEditorClass
+  else
+    Raise EReportError.CreateFmt(SErrUnknownElementName,[AElementName]);
 end;
 end;
 
 
-function TFPReportPaperManager.IndexOfPaper(const AName: string): integer;
+procedure TFPReportElementFactory.RegisterEditorClass(AReportElementClass: TFPReportElementClass;
+  AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
 begin
 begin
-  if not Assigned(FPaperSizes) then
-    Result := -1
+  I:=IndexOfElementClass(aReportElementClass);
+  if I<>-1 then
+    Mappings[i].EditorClass:=AEditorClass
   else
   else
-    Result := FPaperSizes.IndexOf(AName);
+    if AReportElementClass<>Nil then
+      Raise EReportError.CreateFmt(SErrUnknownElementClass,[AReportElementClass.ClassName])
+    else
+      Raise EReportError.CreateFmt(SErrUnknownElementClass,['Nil']);
 end;
 end;
 
 
-procedure TFPReportPaperManager.RegisterPaper(const AName: string; const AWidth, AHeight: TFPReportUnits);
-var
-  I: integer;
-  S: TFPReportPaperSize;
+procedure TFPReportElementFactory.UnRegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+
+Var
+  I : integer;
+
 begin
 begin
-  I := FPaperSizes.IndexOf(AName);
-  if (I = -1) then
-  begin
-    S := TFPReportPaperSize.Create(AWidth, AHeight);
-    FPaperSizes.AddObject(AName, S);
-  end
-  else
-    ReportError(SErrDuplicatePaperName, [AName]);
+  I:=IndexOfElementName(aElementName);
+  if I<>-1 then
+    if Mappings[i].EditorClass=AEditorClass then
+      Mappings[i].EditorClass:=nil;
 end;
 end;
 
 
-{ Got details from Wikipedia [https://simple.wikipedia.org/wiki/Paper_size] }
-procedure TFPReportPaperManager.RegisterStandardSizes;
+procedure TFPReportElementFactory.UnRegisterEditorClass(AReportElementClass: TFPReportElementClass;
+  AEditorClass: TFPReportElementEditorClass);
+Var
+  I : integer;
+
 begin
 begin
-  // As per TFPReportUnits, size is specified in millimetres.
-  RegisterPaper('A3', 297, 420);
-  RegisterPaper('A4', 210, 297);
-  RegisterPaper('A5', 148, 210);
-  RegisterPaper('Letter', 216, 279);
-  RegisterPaper('Legal', 216, 356);
-  RegisterPaper('Ledger', 279, 432);
-  RegisterPaper('DL',	220, 110);
-  RegisterPaper('B5',	176, 250);
-  RegisterPaper('C5',	162, 229);
+  I:=IndexOfElementClass(aReportElementClass);
+  if I<>-1 then
+    if Mappings[i].EditorClass=AEditorClass then
+      Mappings[i].EditorClass:=nil;
 end;
 end;
 
 
-procedure TFPReportPaperManager.GetRegisteredSizes(var AList: TStringList);
+procedure TFPReportElementFactory.RegisterClass(const AElementName: string; AReportElementClass: TFPReportElementClass);
 var
 var
   i: integer;
   i: integer;
 begin
 begin
-  if not Assigned(AList) then
-    Exit;
-  AList.Clear;
-  for i := 0 to FPaperSizes.Count - 1 do
-    AList.Add(PaperNames[i]);
+  I:=IndexOfElementName(AElementName);
+  if I<>-1 then exit;
+  FList.Add(TFPReportClassMapping.Create(AElementName, AReportElementClass));
 end;
 end;
 
 
-procedure DoneReporting;
+function TFPReportElementFactory.CreateInstance(const AElementName: string; AOwner: TComponent): TFPReportElement;
+var
+  i: integer;
 begin
 begin
-  if Assigned(uPaperManager) then
-    FreeAndNil(uPaperManager);
-  TFPReportCustomCheckbox.ImgFalse.Free;
-  TFPReportCustomCheckbox.ImgTrue.Free;
+  Result := nil;
+  for i := 0 to FList.Count - 1 do
+  begin
+    if SameText(Mappings[I].MappingName, AElementName) then
+    begin
+      Result := Mappings[I].ReportElementClass.Create(AOwner);
+      Break; //==>
+    end;
+  end;
+  if Result = nil then
+    ReportError(SErrRegisterUnknownElement, [AElementName]);
 end;
 end;
 
 
-{ TFPTextBlockList }
-
-function TFPTextBlockList.GetItem(AIndex: Integer): TFPTextBlock;
+function TFPReportElementFactory.FindEditorClassForInstance(AInstance: TFPReportElement): TFPReportElementEditorClass;
 begin
 begin
-  Result := TFPTextBlock(inherited GetItem(AIndex));
+  if AInstance<>Nil then
+    Result:=FindEditorClassForInstance(TFPReportElementClass(Ainstance.ClassType))
+  else
+    Result:=Nil;
 end;
 end;
 
 
-procedure TFPTextBlockList.SetItem(AIndex: Integer; AObject: TFPTextBlock);
-begin
-  inherited SetItem(AIndex, AObject);
-end;
+function TFPReportElementFactory.FindEditorClassForInstance(AClass: TFPReportElementClass): TFPReportElementEditorClass;
 
 
-{ TFPReportDataField }
+Var
+  I : Integer;
 
 
-function TFPReportDataField.GetValue: variant;
 begin
 begin
-  Result := Null;
-  if Assigned(Collection) then
-    TFPReportDatafields(Collection).ReportData.DoGetValue(FieldName, Result);
+  I:=IndexOfElementClass(AClass);
+  if I<>-1 then
+    Result:=Mappings[I].EditorClass
+  else
+    Result:=nil;
 end;
 end;
 
 
-procedure TFPReportDataField.Assign(Source: TPersistent);
+procedure TFPReportElementFactory.AssignReportElementTypes(AStrings: TStrings);
 var
 var
-  F: TFPReportDataField;
+  i: integer;
 begin
 begin
-  if Source is TFPReportDataField then
-  begin
-    F := Source as TFPReportDataField;
-    FDisplayWidth := F.FDisplayWidth;
-    FFieldKind := F.FFieldKind;
-    FFieldName := F.FFieldName;
-  end
-  else
-    inherited Assign(Source);
+  AStrings.Clear;
+  for i := 0 to FList.Count - 1 do
+    AStrings.Add(Mappings[I].MappingName);
 end;
 end;
 
 
-{ TFPReportDataFields }
+{ TFPReportCustomDataHeaderBand }
 
 
-function TFPReportDataFields.GetF(AIndex: integer): TFPReportDataField;
+function TFPReportCustomDataHeaderBand.GetReportBandName: string;
 begin
 begin
-  Result := TFPReportDataField(Items[AIndex]);
+  Result := 'DataHeaderBand';
 end;
 end;
 
 
-procedure TFPReportDataFields.SetF(AIndex: integer; const AValue: TFPReportDataField);
+class function TFPReportCustomDataHeaderBand.ReportBandType: TFPReportBandType;
 begin
 begin
-  Items[AIndex] := AValue;
+  Result:=btDataHeader;
 end;
 end;
 
 
-function TFPReportDataFields.AddField(AFieldName: string; AFieldKind: TFPReportFieldKind): TFPReportDataField;
+{ TFPReportCustomDataFooterBand }
+
+function TFPReportCustomDataFooterBand.GetReportBandName: string;
 begin
 begin
-  Result := Add as TFPReportDataField;
-  try
-    Result.FieldName := AFieldName;
-    Result.FieldKind := AFieldKind;
-  except
-    Result.Free;
-    raise;
-  end;
+  Result := 'DataFooterBand';
 end;
 end;
 
 
-function TFPReportDataFields.IndexOfField(const AFieldName: string): integer;
+class function TFPReportCustomDataFooterBand.ReportBandType: TFPReportBandType;
 begin
 begin
-  Result := Count - 1;
-  while (Result >= 0) and (CompareText(AFieldName, GetF(Result).FieldName) <> 0) do
-    Dec(Result);
+  Result:=btDataFooter;
 end;
 end;
 
 
-function TFPReportDataFields.FindField(const AFieldName: string): TFPReportDataField;
+{ ---------------------------------------------------------------------
+  TFPReportLayouter
+  ---------------------------------------------------------------------}
+
+procedure TFPReportLayouter.RemoveTitleBandFromHeaderList;
 var
 var
-  I: integer;
+  idx: integer;
+  lBand: TFPReportCustomBand;
 begin
 begin
-  I := IndexOfField(AFieldName);
-  if (I = -1) then
-    Result := nil
-  else
-    Result := GetF(I);
+  idx := FHeaderList.Find(TFPReportCustomTitleBand, lBand);
+  if idx > -1 then
+    FHeaderList.Delete(idx);
 end;
 end;
 
 
-function TFPReportDataFields.FindField(const AFieldName: string; const AFieldKind: TFPReportFieldKind): TFPReportDataField;
+procedure TFPReportLayouter.RemoveColumnFooterFromFooterList;
 var
 var
-  lIndex: integer;
+  idx: integer;
+  lBand: TFPReportCustomBand;
 begin
 begin
-  lIndex := Count - 1;
-  while (lIndex >= 0) and (not SameText(AFieldName, GetF(lIndex).FieldName)) and (GetF(lIndex).FieldKind <> AFieldKind) do
-      Dec(lIndex);
-
-  if (lIndex = -1) then
-    Result := nil
-  else
-    Result := GetF(lIndex);
+  idx := FFooterList.Find(TFPReportCustomColumnFooterBand, lBand);
+  if idx > -1 then
+    FFooterList.Delete(idx);
 end;
 end;
 
 
-function TFPReportDataFields.FieldByName(const AFieldName: string): TFPReportDataField;
+procedure TFPReportLayouter.UpdateSpaceRemaining(const ABand: TFPReportCustomBand; const AUpdateYPos: boolean = True);
 begin
 begin
-  Result := FindField(AFieldName);
-  if (Result = nil) then
-  begin
-    if Assigned(ReportData) then
-      ReportError(SErrUnknownField, [ReportData.Name, AFieldName])
-    else
-      ReportError(SErrUnknownField, ['', AFieldName]);
-  end;
+  FSpaceLeft := FSpaceLeft - ABand.RTLayout.Height;
+  if AUpdateYPos then
+    FLastYPos := FLastYPos + ABand.RTLayout.Height;
 end;
 end;
 
 
-{ TFPReportData }
+procedure TFPReportLayouter.CommonRuntimeBandProcessing(const aBand: TFPReportCustomBand);
 
 
-procedure TFPReportData.SetDataFields(const AValue: TFPReportDataFields);
 begin
 begin
-  if (FDataFields = AValue) then
-    Exit;
-  FDataFields.Assign(AValue);
+  aBand.PrepareObjects;
+  FRTBand:=FRTCurBand;
+  FRTBand.RecalcLayout;
+  FRTBand.BeforePrint;
+  FRTBand.RTLayout.Top := FLastYPos;
+  FRTBand.RTLayout.Left := FLastXPos;
 end;
 end;
 
 
-function TFPReportData.GetFieldCount: integer;
-begin
-  Result := FDatafields.Count;
-end;
+{ Result of True means ADsgnBand must be skipped. Result of False means ADsgnBand
+  must be rendered (ie: not skipped).  }
+
+function TFPReportLayouter.MaybeSkipBand(const ADsgnBand: TFPReportCustomBand): boolean;
 
 
-function TFPReportData.GetFieldName(Index: integer): string;
 begin
 begin
-  Result := FDatafields[Index].FieldName;
+  Result := True;
+  if ADsgnBand.VisibleOnPage = vpAll then
+  begin
+    // do nothing special
+  end
+  else if (PageNumberPerDesignerPage = 1) then
+  begin // first page rules
+    if (ADsgnBand.VisibleOnPage in [vpFirstOnly, vpFirstAndLastOnly]) then
+    begin
+      // do nothing special
+    end
+    else if (ADsgnBand.VisibleOnPage in [vpNotOnFirst, vpLastOnly, vpNotOnFirstAndLast]) then
+      Exit; // user asked to skip this band
+  end
+  else if (PageNumberPerDesignerPage > 1) then
+  begin  // multi-page rules
+    if ADsgnBand.VisibleOnPage in [vpFirstOnly] then
+      Exit  // user asked to skip this band
+    else if ADsgnBand.VisibleOnPage in [vpNotOnFirst] then
+    begin
+      // do nothing special
+    end
+    else if (not IsFirstPass) then
+    begin // last page rules
+      if (ADsgnBand.VisibleOnPage in [vpLastOnly, vpFirstAndLastOnly]) and (PageNumberPerDesignerPage < PerDesignerPageCount[PageIdx]) then
+        Exit
+      else if (ADsgnBand.VisibleOnPage in [vpNotOnLast, vpFirstOnly, vpNotOnFirstAndLast]) and (PageNumberPerDesignerPage = PerDesignerPageCount[PageIdx]) then
+        Exit; // user asked to skip this band
+    end;
+  end;
+  Result := False;
 end;
 end;
 
 
-function TFPReportData.GetFieldType(AFieldName: string): TFPReportFieldKind;
+{ Result of True means ADsgnBand must be skipped. Result of False means aBand
+  must be rendered (ie: not skipped).  }
+function TFPReportLayouter.ShowPageHeaderBand(const aBand: TFPReportCustomBand) : boolean;
 begin
 begin
-  Result := FDatafields.FieldByName(AFieldName).FieldKind;
+  Result := True;
+  if not (aBand is TFPReportCustomPageHeaderBand) then
+    Exit;
+  if MaybeSkipBand(aBand as TFPReportCustomPageHeaderBand) then
+    Exit;
+  CommonRuntimeBandProcessing(aBand);
+  Result := False;
 end;
 end;
 
 
-function TFPReportData.GetFieldValue(AFieldName: string): variant;
+function TFPReportLayouter.ShowColumnHeaderBand(const aBand: TFPReportCustomBand): boolean;
+
+var
+  lBand: TFPReportCustomBand;
+
 begin
 begin
-  Result := varNull;
-  DoGetValue(AFieldName, Result);
+  Result := False;
+  CommonRuntimeBandProcessing(aBand);
+  { Only once we show the first column header do we take into account
+    the column footer. }
+  lBand := Pages[PageIdx].FindBand(TFPReportCustomColumnFooterBand);
+  FFooterList.Add(lBand);
+  UpdateSpaceRemaining(aBand,false);
 end;
 end;
 
 
-function TFPReportData.GetFieldWidth(AFieldName: string): integer;
+procedure TFPReportLayouter.HandleHeaderBands;
+
+Var
+  I : Integer;
+  lBand: TFPReportCustomBand;
+
 begin
 begin
-  Result := FDataFields.FieldByName(AFieldName).DisplayWidth;
+  { Show all header bands }
+  for I := 0 to FHeaderList.Count - 1 do
+    begin
+    lBand := FHeaderList[I];
+    if lBand is TFPReportCustomPageHeaderBand then
+      begin
+      if ShowPageHeaderBand(lBand) then
+        Continue;
+      end
+    else if lBand is TFPReportCustomColumnHeaderBand then
+      begin
+      if ShowColumnHeaderBand(lBand) then
+        Continue;
+      end
+    else
+      CommonRuntimeBandProcessing(lBand);
+    UpdateSpaceRemaining(FRTBand);
+    end;
 end;
 end;
 
 
-function TFPReportData.CreateDataFields: TFPReportDataFields;
+procedure TFPReportLayouter.HandleFooterBands;
+
+Var
+  lBand : TFPReportCustomBand;
+
 begin
 begin
-  Result := TFPReportDataFields.Create(TFPReportDataField);
+  { Show footer band if it exists }
+  lBand := FFooterList.Find(TFPReportCustomPageFooterBand);
+  if Assigned(lBand) then
+    ShowFooterBand(TFPReportCustomPageFooterBand(lBand));
 end;
 end;
 
 
-procedure TFPReportData.DoGetValue(const AFieldName: string; var AValue: variant);
+Procedure TFPReportLayouter.ShowColumnFooterBand(APage: TFPReportCustomPage; ABand: TFPReportCustomColumnFooterBand);
+
+var
+  lBandCount: integer;
+  lOverflowBand: TFPReportCustomBand;
+  lFooterBand : TFPReportCustomColumnFooterBand;
+
 begin
 begin
-  AValue := Null;
+  lBandCount := FRTPage.BandCount - 1;
+  lOverflowBand := FRTPage.Bands[lBandCount]; { store reference to band that caused the new column }
+  CommonRuntimeBandProcessing(ABand);
+  lFooterBand := TFPReportCustomColumnFooterBand(FRTBand);
+  if FPageFooterYPos = -1 then
+    FRTBand.RTLayout.Top := (APage.RTLayout.Top + APage.RTLayout.Height) - FRTBand.RTLayout.Height
+  else
+    FRTBand.RTLayout.Top := FPageFooterYPos - FRTBand.RTLayout.Height;
+  if lFooterBand.FooterPosition = fpAfterLast then
+    begin
+    if FNewColumn or FOverflowed then
+      { take height of overflowed band into account }
+      FRTBand.RTLayout.Top := FLastYPos - lOverflowBand.RTLayout.Height
+    else
+      FRTBand.RTLayout.Top := FLastYPos;
+    end;
 end;
 end;
 
 
-procedure TFPReportData.DoInitDataFields;
+function TFPReportLayouter.NoSpaceRemaining: boolean;
+
 begin
 begin
-  // Do nothing.
+  Result:=FSpaceLeft <= 0;
+  if Result then
+    begin
+    if FMultiColumn and (FCurrentColumn < Pages[PageIdx].ColumnCount) then
+      begin
+      FNewColumn := True;
+      end
+    else
+      begin
+      FOverflowed := True;
+      FNewPage := True;
+      end;
+    end
 end;
 end;
 
 
-procedure TFPReportData.DoOpen;
+
+procedure TFPReportLayouter.StartNewColumn;
+
+var
+  lIdx: integer;
+  lBandCount: integer;
+  lBand: TFPReportCustomBand;
+  lOverflowBand: TFPReportCustomBand;
+
 begin
 begin
-  // Do nothing
+  if Assigned(FLastDsgnDataBand) then
+    report.ClearDataBandLastTextValues(FLastDsgnDataBand);
+  if FMultiColumn and (FFooterList.Find(TFPReportCustomColumnFooterBand) <> nil) then
+    lBandCount := FRTPage.BandCount - 2  // skip over the ColumnFooter band
+  else
+    lBandCount := FRTPage.BandCount - 1;
+  lOverflowBand := FRTPage.Bands[lBandCount]; { store reference to band that caused the new column }
+  FSpaceLeft := Pages[PageIdx].Layout.Height; // original designer page
+  FRTPage := TFPReportCustomPage(RTObjects[RTCurPageIdx]);
+  FLastYPos := FRTPage.RTLayout.Top;
+  FLastXPos := FLastXPos + lOverflowBand.RTLayout.Width + Pages[PageIdx].ColumnGap;
+  { Adjust starting Y-Pos based on bands in lHeaderList. }
+  for lIdx := 0 to FHeaderList.Count-1 do
+    begin
+    lBand := FHeaderList[lIdx];
+    if lBand is TFPReportCustomPageHeaderBand then
+      begin
+      if MaybeSkipBand(lBand) then
+        Continue;
+      end
+    else if lBand is TFPReportCustomColumnHeaderBand then
+      begin
+      if ShowColumnHeaderBand(lBand) then
+        Continue;
+      end;
+    UpdateSpaceRemaining(lBand,True);
+    end;
+  inc(FCurrentColumn);
+  { If footer band exists, reduce available space }
+  lBand := FRTPage.FindBand(TFPReportCustomPageFooterBand);
+  if Assigned(lBand) then
+    UpdateSpaceRemaining(lBand, False);
+
+  if NoSpaceRemaining then
+    Exit;
+  { Fix position of last band that caused the new column }
+  lOverflowBand.RTLayout.Left := FLastXPos;
+  lOverflowBand.RTLayout.Top := FLastYPos;
+  { Adjust the next starting point of the next data band. }
+  UpdateSpaceRemaining(lOverflowBand);
+  FNewColumn := False;
 end;
 end;
 
 
-procedure TFPReportData.DoFirst;
+procedure TFPReportLayouter.HandleOverflowed;
+
+var
+  lPrevRTPage: TFPReportCustomPage;
+  lOverflowBand: TFPReportCustomBand;
+  lBandCount: integer;
+
 begin
 begin
-  // Do nothing
+  FOverflowed := False;
+  lPrevRTPage := TFPReportCustomPage(RTObjects[RTCurPageIdx-1]);
+  if FMultiColumn and (FFooterList.Find(TFPReportCustomColumnFooterBand) <> nil) then
+    lBandCount := lPrevRTPage.BandCount - 2  // skip over the ColumnFooter band
+  else
+    lBandCount := lPrevRTPage.BandCount - 1;
+  lOverflowBand := lPrevRTPage.Bands[lBandCount]; // get the last band - the one that didn't fit
+  lPrevRTPage.RemoveChild(lOverflowBand);
+  FRTPage.AddChild(lOverflowBand);
+
+  { Fix position of last band that caused the overflow }
+  lOverflowBand.RTLayout.Top := FLastYPos;
+  lOverflowBand.RTLayout.Left := FLastXPos;
+  UpdateSpaceRemaining(lOverflowBand);
 end;
 end;
 
 
-procedure TFPReportData.DoNext;
+procedure TFPReportLayouter.ShowFooterBand(aBand: TFPReportCustomPageFooterBand);
+
 begin
 begin
-  // Do nothing
+  FPageFooterYPos := -1;
+  if MaybeSkipBand(aBand) then
+    Exit;
+  aBand.PrepareObjects;
+  FRTBand := FRTCurBand;
+  FRTBand.RecalcLayout;
+  FRTBand.BeforePrint;
+  FRTBand.RTLayout.Top := (FRTPage.RTLayout.Top + FRTPage.RTLayout.Height) - FRTBand.RTLayout.Height;
+  FPageFooterYPos := FRTBand.RTLayout.Top;
+  // We don't adjust lLastYPos because this is a page footer
+  UpdateSpaceRemaining(FRTBand, False);
 end;
 end;
 
 
-procedure TFPReportData.DoClose;
+procedure TFPReportLayouter.PopulateHeaderList(APage: TFPReportCustomPage);
+
 begin
 begin
-  // Do nothing
+  FHeaderList.Clear;
+  FHeaderList.Add(APage.FindBand(TFPReportCustomPageHeaderBand));
+  FHeaderList.Add(APage.FindBand(TFPReportCustomTitleBand));
+  if FMultiColumn then
+    FHeaderList.Add(Pages[PageIdx].FindBand(TFPReportColumnHeaderBand));
 end;
 end;
 
 
-function TFPReportData.DoEOF: boolean;
+function TFPReportLayouter.GetPerDesignerPageCount(Index : Cardinal): Cardinal;
 begin
 begin
-  Result := False;
+  Result:=Report.FPerDesignerPageCount[Index];
 end;
 end;
 
 
-constructor TFPReportData.Create(AOwner: TComponent);
+Procedure TFPReportLayouter.InitRTCurPageIdx;
+
 begin
 begin
-  inherited Create(AOwner);
-  FDatafields := CreateDataFields;
-  FDatafields.FReportData := Self;
+  Report.FRTCurPageIdx:=-1;
 end;
 end;
 
 
-destructor TFPReportData.Destroy;
+function TFPReportLayouter.GetRTCurPageIdx: Integer;
 begin
 begin
-  FreeAndNil(FDatafields);
-  inherited Destroy;
+  Result:=Report.FRTCurPageIdx;
 end;
 end;
 
 
-procedure TFPReportData.InitFieldDefs;
+function TFPReportLayouter.GetRTObjects: TFPList;
 begin
 begin
-  if FIsOpened then
-    ReportError(SErrInitFieldsNotAllowedAfterOpen);
-  DoInitDataFields;
+  Result:=Report.RTObjects;
 end;
 end;
 
 
-procedure TFPReportData.Open;
+procedure TFPReportLayouter.SetGetPerDesignerPageCount(Index : Cardinal; AValue: Cardinal);
 begin
 begin
-  if Assigned(FOnOpen) then
-    FOnOpen(Self);
-  DoOpen;
-  InitFieldDefs;
-  FIsOpened := True;
-  FRecNo := 1;
+  Report.FPerDesignerPageCount[Index]:=AValue;
 end;
 end;
 
 
-procedure TFPReportData.First;
+
+procedure TFPReportLayouter.SetPageNumberPerDesignerPage(aValue: Integer);
 begin
 begin
-  if Assigned(FOnFirst) then
-    FOnFirst(Self);
-  DoFirst;
-  FRecNo := 1;
+  Report.FPageNumberPerDesignerPage:=aValue;
 end;
 end;
 
 
-procedure TFPReportData.Next;
+procedure TFPReportLayouter.SetPageCount(aCount: Integer);
 begin
 begin
-  Inc(FRecNo);
-  if Assigned(FOnNext) then
-    FOnNext(Self);
-  DoNext;
+  Report.FPageCount:=aCount;
 end;
 end;
 
 
-procedure TFPReportData.Close;
+function TFPReportLayouter.GetPage(AIndex: integer): TFPReportCustomPage;
 begin
 begin
-  if Assigned(FOnClose) then
-    FOnClose(Self);
-  DoClose;
-  FIsOpened := False;
-  FRecNo := -1;
+  Result:=Report.Pages[AIndex];
 end;
 end;
 
 
-function TFPReportData.EOF: boolean;
+function TFPReportLayouter.FRTCurBand: TFPReportCustomBand;
 begin
 begin
-  Result := False;
-  if Assigned(FOnGetEOF) then
-    FOnGetEOF(Self, Result);
-  if not Result then
-    Result := DoEOF;
+  Result:=Report.FRTCurBand;
 end;
 end;
 
 
-procedure TFPReportData.GetFieldList(List: TStrings);
-var
-  I: integer;
+function TFPReportLayouter.GetPageNumberPerDesignerPage: Integer;
 begin
 begin
-  List.BeginUpdate;
-  try
-    List.Clear;
-    for I := 0 to FDataFields.Count - 1 do
-      List.add(FDataFields[I].FieldName);
-  finally
-    List.EndUpdate;
-  end;
+  Result:=Report.FPageNumberPerDesignerPage;
 end;
 end;
 
 
-function TFPReportData.IndexOfField(const AFieldName: string): Integer;
+function TFPReportLayouter.IsFirstPass: Boolean;
 begin
 begin
-  Result:=  FDataFields.IndexOfField(AFieldName);
+  Result:=Report.IsFirstPass;
 end;
 end;
 
 
-function TFPReportData.HasField(const AFieldName: string): boolean;
+procedure TFPReportLayouter.PopulateFooterList(APage: TFPReportCustomPage);
 begin
 begin
-  Result := FDataFields.IndexOfField(AFieldName) <> -1;
+  FFooterList.Clear;
+  FFooterList.Add(APage.FindBand(TFPReportCustomPageFooterBand));
 end;
 end;
 
 
+procedure TFPReportLayouter.IncPageNumber;
 
 
-{ TFPReportClassMapping }
-
-function TFPReportClassMapping.IndexOfExportRenderer(
-  AClass: TFPReportExporterClass): Integer;
 begin
 begin
-  Result:=Length(FRenderers)-1;
-  While (Result>=0) and (FRenderers[Result].aClass<>AClass) do
-    Dec(Result);
+  Inc(Report.FPageNumber);
 end;
 end;
 
 
-constructor TFPReportClassMapping.Create(const AMappingName: string; AElementClass: TFPReportElementClass);
+procedure TFPReportLayouter.InitPageNumber;
 begin
 begin
-  FMappingName :=  AMappingName;
-  FReportElementClass := AElementClass;
+  Report.FPageNumber:=0;
 end;
 end;
 
 
-function TFPReportClassMapping.AddRenderer(aExporterClass: TFPReportExporterClass; aCallback: TFPReportElementExporterCallBack ): TFPReportElementExporterCallBack;
-
-Var
-  I : Integer;
+procedure TFPReportLayouter.IncPageNumberPerDesignerPage;
 
 
 begin
 begin
-  Result:=nil;
-  I:=IndexOfExportRenderer(aExporterClass);
-  if (I=-1) then
-    begin
-    I:=Length(FRenderers);
-    SetLength(FRenderers,I+1);
-    FRenderers[i].aClass:=aExporterClass;
-    FRenderers[i].aCallback:=Nil;
-    end;
-  Result:=FRenderers[i].aCallback;
-  FRenderers[i].aCallback:=aCallback;
+  inc(Report.FPageNumberPerDesignerPage);
 end;
 end;
 
 
-function TFPReportClassMapping.FindRenderer(aClass: TFPReportExporterClass): TFPReportElementExporterCallBack;
+procedure TFPReportLayouter.StartNewPage;
 
 
-Var
-  I : Integer;
+begin
+  if Assigned(FLastDsgnDataBand) then
+    report.ClearDataBandLastTextValues(FLastDsgnDataBand);
+  FSpaceLeft := Pages[PageIdx].Layout.Height; // original designer page
+  Pages[PageIdx].PrepareObjects;  // creates a new page object
+  //not needed here, it's done in TFPReportCustomPage.PrepareObjects: FRTCurPageIdx := RTObjects.Count-1;
+  FRTPage := TFPReportCustomPage(RTObjects[RTCurPageIdx]);
+  FLastYPos := FRTPage.RTLayout.Top;
+  FLastXPos := FRTPage.RTLayout.Left;
+  IncPageNumber;
+  FCurrentColumn := 1;
+  FNewColumn := False;
+  if IsFirstPass then
+    PerDesignerPageCount[PageIdx] := PerDesignerPageCount[PageIdx] + 1;
+  if (PageNumberPerDesignerPage = 1) then
+    RemoveTitleBandFromHeaderList;
+  IncPageNumberPerDesignerPage;
+  HandleHeaderBands;
+  HandleFooterBands;
+  FNewPage := False;
+end;
+
+Procedure TFPReportLayouter.CheckRemaining(CheckMulticolumn : Boolean = True);
 
 
 begin
 begin
-  I:=IndexOfExportRenderer(aClass);
-  if I<>-1 then
-    Result:=FRenderers[I].aCallback
-  else
-    Result:=Nil;
+  if NoSpaceRemaining then
+    begin
+    if CheckMulticolumn and FMultiColumn then
+      begin
+      FCurrentRTColumnFooterBand := TFPReportCustomColumnFooterBand(FFooterList.Find(TFPReportCustomColumnFooterBand));
+      if Assigned(FCurrentRTColumnFooterBand) then
+        ShowColumnFooterBand(FRTPage, FCurrentRTColumnFooterBand);
+      end;
+    CheckNewOrOverFlow(CheckMulticolumn);
+    end;
 end;
 end;
 
 
-{ TFPReportElementFactory }
+procedure TFPReportLayouter.ShowDataBand(const aBand: TFPReportCustomDataBand);
+
+var
+  aChildBand: TFPReportChildBand;
+  aDataBand: TFPReportCustomBand;
 
 
-function TFPReportElementFactory.GetM(Aindex : integer): TFPReportClassMapping;
 begin
 begin
-  Result:=TFPReportClassMapping(FList[AIndex]);
+  FLastDsgnDataBand := aBand;
+  CommonRuntimeBandProcessing(aBand);
+  if HandleBandVisibility(FRTBand,True) then
+    begin
+    aDataBand := FRTBand;
+    { process any child bands off of DataBand }
+    aChildBand := aDataBand.ChildBand;
+    while aChildBand <> nil do
+      begin
+      CommonRuntimeBandProcessing(aChildBand);
+      HandleBandVisibility(FRTBand,True);
+      aChildBand := aChildBand.ChildBand;
+      end;  { while ChildBand <> nil }
+    end
 end;
 end;
 
 
-function TFPReportElementFactory.IndexOfElementName(const AElementName: string): Integer;
+function TFPReportLayouter.HandleBandVisibility(aBand: TFPReportCustomBand; doRecalcLayout: Boolean): Boolean;
 
 
 begin
 begin
-  Result:=Flist.Count-1;
-  While (Result>=0) and not SameText(Mappings[Result].MappingName, AElementName) do
-    Dec(Result);
+  Result:=aBand.EvaluateVisibility;
+  if Result then
+    begin
+    if DoRecalcLayout then
+      RecalcBandLayout(FRTBand);
+    UpdateSpaceRemaining(aBand);
+    CheckRemaining(True);
+    end
+  else
+    begin
+    // remove invisible band
+    FRTPage.RemoveChild(aBand);
+    FreeAndNil(aBand);
+    end;
 end;
 end;
 
 
-function TFPReportElementFactory.IndexOfElementClass(const AElementClass: TFPReportElementClass): Integer;
+procedure TFPReportLayouter.ShowDataHeaderBand(const aBand: TFPReportCustomDataHeaderBand);
 
 
 begin
 begin
-  Result:=Flist.Count-1;
-  While (Result>=0) and (Mappings[Result].ReportElementClass<>AElementClass) do
-    Dec(Result);
+  if FDataHeaderPrinted then
+    Exit; // nothing further to do
+  CommonRuntimeBandProcessing(aBand);
+  if HandleBandVisibility(FRTBand,False) then
+    FDataHeaderPrinted := True;
 end;
 end;
 
 
-constructor TFPReportElementFactory.Create;
+procedure TFPReportLayouter.ShowDataFooterBand(const aBand: TFPReportCustomDataFooterBand);
+
 begin
 begin
-  FList := TFPObjectList.Create;
+  CommonRuntimeBandProcessing(aBand);
+  HandleBandVisibility(FRTBand,False);
 end;
 end;
 
 
-destructor TFPReportElementFactory.Destroy;
+procedure TFPReportLayouter.ShowDetailBand(const AMasterBand: TFPReportCustomDataBand);
+
+var
+  lDsgnDetailBand: TFPReportCustomDataBand;
+  lDetailBand: TFPReportCustomBand;
+  lDetailBandList: TBandList;
+  lData: TFPReportData;
+  i: integer;
+
 begin
 begin
-  FList.Free;
-  inherited Destroy;
+  if AMasterBand = nil then
+    Exit;
+  lDsgnDetailBand := nil;
+  lDetailBandList := TBandList.Create;
+  try
+    { collect bands of interest }
+    for i := 0 to Pages[PageIdx].BandCount-1 do
+      begin
+      lDetailBand := Pages[PageIdx].Bands[i];
+      if (lDetailBand is TFPReportCustomDataBand)
+        and (TFPReportCustomDataBand(lDetailBand).MasterBand = AMasterBand)
+        and (TFPReportCustomDataBand(lDetailBand).Data <> nil) then
+          lDetailBandList.Add(lDetailBand);
+      end;
+    if lDetailBandList.Count = 0 then
+      exit;  // nothing further to do
+    lDetailBandList.Sort(@SortDataBands);
+    { process Detail bands }
+    for i := 0 to lDetailBandList.Count-1 do
+      begin
+      lDsgnDetailBand := TFPReportCustomDataBand(lDetailBandList[i]);
+      lData := lDsgnDetailBand.Data;
+      if not lData.IsOpened then
+        begin
+        lData.Open;
+        Report.InitializeExpressionVariables(Pages[PageIdx], lData);
+        Report.CacheMemoExpressions(PageIdx, lData);
+        end;
+      lData.First;
+      if (not lData.EOF) and (lDsgnDetailBand.HeaderBand <> nil) then
+        ShowDataHeaderBand(lDsgnDetailBand.HeaderBand);
+      while not lData.EOF do
+        begin
+        If Report.TwoPass and IsFirstPass then
+          Report.Variables.BuildAggregates;
+        inc(FDataLevelStack);
+        ShowDataBand(lDsgnDetailBand);
+        ShowDetailBand(lDsgnDetailBand);
+        dec(FDataLevelStack);
+        lData.Next;
+        end;  { while not lData.EOF }
+      FDataHeaderPrinted := False;
+      CheckNewOrOverFlow;
+      // only print if we actually had data
+      if (lData.RecNo > 1) and (lDsgnDetailBand.FooterBand <> nil) then
+        ShowDataFooterBand(lDsgnDetailBand.FooterBand);
+      lDsgnDetailBand := nil;
+      end;
+  finally
+    lDetailBandList.Free;
+  end;
 end;
 end;
 
 
-function TFPReportElementFactory.FindRenderer(aClass: TFPReportExporterClass;
-  AElement: TFPReportElementClass): TFPReportElementExporterCallBack;
+Procedure TFPReportLayouter.PrepareGroupHeaderBand(aBand : TFPReportCustomGroupHeaderBand);
 
 
 Var
 Var
-  I : Integer;
+  CurrGroup : String;
+  lBand : TFPReportCustomBand;
 
 
 begin
 begin
-  Result:=nil;
-  I:=IndexOfElementClass(aElement);
-  if I<>-1 then
-    Result:=Mappings[i].FindRenderer(aClass);
+  // Writeln('Found group with expr: ',TFPReportCustomGroupHeaderBand(lDsgnBand).GroupCondition);
+  CurrGroup := aBand.Evaluate;
+  // Writeln('Group new ? "',lLastGroupCondition,'" <> "', CurrGroup,'"');
+  if (FLastGroupCondition = CurrGroup) then
+    Exit;
+  FNewGroupHeader := True;
+  { process group footer }
+  if Assigned(aBand.GroupFooter) then
+    begin
+    Report.FRTUseLastValues:=true;
+    try
+      lBand := aBand.GroupFooter;
+      CommonRuntimeBandProcessing(lBand);
+    finally
+      Report.FRTUseLastValues:=false;
+    end;
+    UpdateSpaceRemaining(FRTBand);
+    if NoSpaceRemaining then
+      CheckNewOrOverFlow;
+    end;
 end;
 end;
 
 
-function TFPReportElementFactory.FindImageRenderer(
-  AElement: TFPReportElementClass): TFPReportImageRenderCallBack;
+Procedure TFPReportLayouter.HandleGroupBands;
+
 Var
 Var
   I : Integer;
   I : Integer;
+  lBand :  TFPReportCustomBand;
 
 
 begin
 begin
-  Result:=nil;
-  I:=IndexOfElementClass(aElement);
-  if I<>-1 then
-    Result:=Mappings[i].ImageRenderCallback;
+  if FLastGroupCondition = '' then
+    FNewGroupHeader := True
+  else
+    for I := 0 to Report.FBands.Count-1 do
+      begin
+      lBand := TFPReportCustomBand(Report.FBands[i]);
+      if lBand is TFPReportCustomGroupHeaderBand then
+        PrepareGroupHeaderBand(lBand as TFPReportCustomGroupHeaderBand);
+      end;
+  if not FNewGroupHeader then
+    Exit;
+  for i := 0 to Report.FBands.Count-1 do
+    begin
+    lBand := TFPReportCustomBand(Report.FBands[i]);
+    if lBand is TFPReportCustomGroupHeaderBand then
+      begin
+      if Assigned(FLastDsgnDataBand) then
+        Report.ClearDataBandLastTextValues(FLastDsgnDataBand);
+      CommonRuntimeBandProcessing(lBand);
+      FLastGroupCondition := TFPReportGroupHeaderBand(FRTBand).GroupConditionValue;
+      if Not lBand.EvaluateVisibility  then
+        begin
+        FRTPage.RemoveChild(FRTBand);
+        FRTBand.Free;
+        Continue; { process next band }
+        end;
+      UpdateSpaceRemaining(FRTBand);
+      if NoSpaceRemaining then
+        Break;  { break out of FOR loop }
+      end;
+    end;
+  FNewGroupHeader := False;
+  FDataHeaderPrinted := False;
 end;
 end;
 
 
-function TFPReportElementFactory.RegisterImageRenderer(AElement: TFPReportElementClass; ARenderer: TFPReportImageRenderCallBack
-  ): TFPReportImageRenderCallBack;
+Procedure TFPReportLayouter.HandleDatabands;
+
 Var
 Var
-  I : Integer;
+  D : TFPReportCustomDataBand;
+  i : Integer;
+  lBand : TFPReportCustomBand;
+
 begin
 begin
-  Result:=nil;
-  I:=IndexOfElementClass(aElement);
-  if I<>-1 then
+  for I := 0 to Report.FBands.Count-1 do
     begin
     begin
-    Result:=Mappings[i].ImageRenderCallback;
-    Mappings[i].ImageRenderCallback:=ARenderer;
+    lBand := TFPReportCustomBand(Report.FBands[I]);
+    if (lBand is TFPReportCustomDataBand) then
+      begin
+      inc(FDataLevelStack);
+      D:=TFPReportCustomDataBand(lBand);
+      if D.HeaderBand <> nil then
+        ShowDataHeaderBand(D.HeaderBand);
+      ShowDataBand(D);
+      ShowDetailBand(D);
+      dec(FDataLevelStack);
+      end;
     end;
     end;
 end;
 end;
 
 
-function TFPReportElementFactory.RegisterElementRenderer(AElement: TFPReportElementClass; ARenderClass: TFPReportExporterClass;
-  ARenderer: TFPReportElementExporterCallBack): TFPReportElementExporterCallBack;
+procedure TFPReportLayouter.HandleGroupFooters;
+
 Var
 Var
   I : Integer;
   I : Integer;
+  lBand : TFPReportCustomBand;
+
 begin
 begin
-  Result:=nil;
-  I:=IndexOfElementClass(aElement);
-  if (I<>-1) then
-    Result:=Mappings[i].AddRenderer(aRenderClass,ARenderer);
+  for I := 0 to Report.FBands.Count-1 do
+    begin
+    lBand := TFPReportCustomBand(Report.FBands[I]);
+    if lBand is TFPReportCustomGroupHeaderBand then
+      begin
+      lBand:=(lBand as TFPReportCustomGroupHeaderBand).GroupFooter;
+      { We are allowed to use design Layout.Height instead of RTLayout.Height
+        because this band appears outside the data loop, thus memos will not
+        grow. Height of the band is as it was at design time. }
+      if lBand.Layout.Height > FSpaceLeft then
+        StartNewPage;
+      CommonRuntimeBandProcessing(lBand);
+      UpdateSpaceRemaining(FRTBand);
+      Break;
+      end;
+    end;
 end;
 end;
 
 
-procedure TFPReportElementFactory.RegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
-
-Var
-  I : integer;
+Procedure TFPReportLayouter.ClearBandList;
 
 
 begin
 begin
-  I:=IndexOfElementName(aElementName);
-  if I<>-1 then
-    Mappings[i].EditorClass:=AEditorClass
-  else
-    Raise EReportError.CreateFmt(SErrUnknownElementName,[AElementName]);
+  Report.FBands.Clear;
 end;
 end;
 
 
-procedure TFPReportElementFactory.RegisterEditorClass(AReportElementClass: TFPReportElementClass;
-  AEditorClass: TFPReportElementEditorClass);
+Procedure TFPReportLayouter.InitBandList(aPage : TFPReportCustomPage; aDataLoop : TFPReportData);
 
 
 Var
 Var
-  I : integer;
+  I : Integer;
+  lBand : TFPReportCustomBand;
 
 
 begin
 begin
-  I:=IndexOfElementClass(aReportElementClass);
-  if I<>-1 then
-    Mappings[i].EditorClass:=AEditorClass
-  else
-    if AReportElementClass<>Nil then
-      Raise EReportError.CreateFmt(SErrUnknownElementClass,[AReportElementClass.ClassName])
+  // Create a list of band that need to be printed as page headers
+  PopulateHeaderList(aPage);
+  // Create a list of bands that need to be printed as page footers
+  PopulateFooterList(aPage);
+  // find Bands of interest
+  ClearBandList;
+  for I := 0 to aPage.BandCount-1 do
+    begin
+    lBand := aPage.Bands[I];
+    if (lBand is TFPReportCustomDataBand) then
+      begin
+      if TFPReportCustomDataBand(lBand).Data = aDataLoop then
+        begin
+        { Do a quick sanity check - we may not have more than one master data band }
+        if FFoundDataBand then
+          ReportError(SErrMultipleDataBands);
+        Report.FBands.Add(lBand);
+        FFoundDataBand := True;
+        end
+      else
+        continue; // it's a databand but not for the current data loop
+      end
     else
     else
-      Raise EReportError.CreateFmt(SErrUnknownElementClass,['Nil']);
-end;
+      begin
+      if (lBand is TFPReportCustomGroupHeaderBand) and (TFPReportCustomGroupHeaderBand(lBand).GroupHeader <> nil) then
+        continue; // this is not the toplevel GroupHeader Band.
+      if lBand is TFPReportCustomGroupFooterBand then
+        continue; // we will get the Footer from the GroupHeaderBand.FooterBand property
+      Report.FBands.Add(aPage.Bands[I]);  { all non-data bands are of interest }
+      end;
 
 
-procedure TFPReportElementFactory.UnRegisterEditorClass(const AElementName: string; AEditorClass: TFPReportElementEditorClass);
+    if lBand is TFPReportCustomGroupHeaderBand then
+      begin
+      FHasGroupBand := True;
+      if Assigned(TFPReportCustomGroupHeaderBand(lBand).GroupFooter) then
+        FHasGroupFooter := True;
+      end
+    else if lBand is TFPReportCustomSummaryBand then
+      FHasReportSummaryBand := True;
+    end;
+end;
 
 
-Var
-  I : integer;
+Procedure TFPReportLayouter.CheckNewOrOverFlow(CheckMulticolumn: Boolean = True);
 
 
 begin
 begin
-  I:=IndexOfElementName(aElementName);
-  if I<>-1 then
-    if Mappings[i].EditorClass=AEditorClass then
-      Mappings[i].EditorClass:=nil;
+  if CheckMulticolumn and FNewColumn then
+    StartNewColumn;
+  if FNewPage then
+    StartNewPage;
+  { handle overflowed bands. Remove from old page, add to new page }
+  if FOverflowed then
+    HandleOverflowed;
 end;
 end;
 
 
-procedure TFPReportElementFactory.UnRegisterEditorClass(AReportElementClass: TFPReportElementClass;
-  AEditorClass: TFPReportElementEditorClass);
+Procedure TFPReportLayouter.RunDataLoop(lPageIdx : Integer; lPageData : TFPReportData);
+
 Var
 Var
   I : integer;
   I : integer;
+  lBand : TFPReportCustomBand;
 
 
 begin
 begin
-  I:=IndexOfElementClass(aReportElementClass);
-  if I<>-1 then
-    if Mappings[i].EditorClass=AEditorClass then
-      Mappings[i].EditorClass:=nil;
+  if not lPageData.IsOpened then
+    lPageData.Open;
+  if IsFirstPass then
+    begin
+    Report.InitializeExpressionVariables(Pages[lPageIdx], lPageData);
+    Report.CacheMemoExpressions(lPageIdx, lPageData);
+    end
+  else
+    Report.Variables.InitSecondPass;
+  lPageData.First;
+  InitBandList(Pages[lPageIdx],lPageData);
+  while not lPageData.EOF do
+    begin
+    if Report.TwoPass and IsFirstPass then
+      Report.Variables.BuildAggregates;
+    CheckNewOrOverFlow(True);
+    if FHasGroupBand then
+      HandleGroupBands;
+    { handle overflow possibly caused by Group Band just processed. }
+    if FOverflowed then
+      Continue;
+    HandleDataBands;
+    lPageData.Next;
+    end;
+  if not (Report.TwoPass and IsFirstPass) then
+    begin
+    CheckNewOrOverFlow(True);
+    // only print if we actually had data
+    if (lPageData.RecNo > 1) then
+      begin
+      for I := 0 to Report.FBands.Count-1 do
+        begin
+        lBand := TFPReportCustomBand(Report.FBands[I]);
+        if lBand is TFPReportCustomDataBand then
+          if TFPReportCustomDataBand(lBand).FooterBand <> nil then
+            ShowDataFooterBand(TFPReportCustomDataBand(lBand).FooterBand);
+        end;
+      end;
+    { Process ColumnFooterBand as needed }
+    if FMultiColumn then
+      begin
+      FCurrentRTColumnFooterBand:= TFPReportCustomColumnFooterBand(FFooterList.Find(TFPReportCustomColumnFooterBand));
+      if Assigned(FCurrentRTColumnFooterBand) then
+        ShowColumnFooterBand(FRTPage, FCurrentRTColumnFooterBand);
+      end;
+    { ColumnFooter could have caused a new column or page }
+    CheckNewOrOverFlow(True);
+    if FHasGroupFooter then
+      HandleGroupFooters;
+    end;
+  lPageData.Close;
 end;
 end;
 
 
-procedure TFPReportElementFactory.RegisterClass(const AElementName: string; AReportElementClass: TFPReportElementClass);
-var
-  i: integer;
-begin
-  I:=IndexOfElementName(AElementName);
-  if I<>-1 then exit;
-  FList.Add(TFPReportClassMapping.Create(AElementName, AReportElementClass));
-end;
+Procedure TFPReportLayouter.InitPass(aPassIdx : Integer);
 
 
-function TFPReportElementFactory.CreateInstance(const AElementName: string; AOwner: TComponent): TFPReportElement;
-var
-  i: integer;
 begin
 begin
-  Result := nil;
-  for i := 0 to FList.Count - 1 do
-  begin
-    if SameText(Mappings[I].MappingName, AElementName) then
-    begin
-      Result := Mappings[I].ReportElementClass.Create(AOwner);
-      Break; //==>
-    end;
-  end;
-  if Result = nil then
-    ReportError(SErrRegisterUnknownElement, [AElementName]);
+  Report.FIsFirstPass := (aPassIdx = 1);
+  Report.EmptyRTObjects;
+  FHeaderList.Clear;
+  FFooterList.Clear;
+  ClearBandList;
+  InitRTCurPageIdx;
+  FOverflowed := False;
+  FHasGroupBand := False;
+  FHasGroupFooter := False;
+  FHasReportSummaryBand := False;
+  FDataHeaderPrinted := False;
+  FLastGroupCondition := '';
+  InitPageNumber;
+  FDataLevelStack := 0;
 end;
 end;
 
 
-function TFPReportElementFactory.FindEditorClassForInstance(AInstance: TFPReportElement): TFPReportElementEditorClass;
+Procedure TFPReportLayouter.InitDesignPage(aPageIdx : integer);
+
 begin
 begin
-  if AInstance<>Nil then
-    Result:=FindEditorClassForInstance(TFPReportElementClass(Ainstance.ClassType))
-  else
-    Result:=Nil;
+  FPageIdx:=aPageIdx;
+  FNewPage := True;
+  FNewGroupHeader := True;
+  FCurrentColumn := 1;
+  FMultiColumn := Pages[aPageIdx].ColumnCount > 1;
+  FNewColumn := False;
+  FPageFooterYPos := -1;
+  PageNumberPerDesignerPage := 0;
+  FFoundDataBand := False;
+  FLastDsgnDataBand := nil;
 end;
 end;
 
 
-function TFPReportElementFactory.FindEditorClassForInstance(AClass: TFPReportElementClass): TFPReportElementEditorClass;
+Procedure TFPReportLayouter.HandleReportSummaries;
 
 
 Var
 Var
-  I : Integer;
+  I : integer;
+  lBand : TFPReportCustomBand;
 
 
 begin
 begin
-  I:=IndexOfElementClass(AClass);
-  if I<>-1 then
-    Result:=Mappings[I].EditorClass
-  else
-    Result:=nil;
+  for I:=0 to Report.FBands.Count-1 do
+    begin
+    lBand := TFPReportCustomBand(Report.FBands[I]);
+    if lBand is TFPReportCustomSummaryBand then
+      begin
+      { We are allowed to use design Layout.Height instead of RTLayout.Height
+        because this band appears outside the data loop, thus memos will not
+        grow. Height of the band is as it was at design time. }
+      if (TFPReportCustomSummaryBand(lBand).StartNewPage) or (lBand.Layout.Height > FSpaceLeft) then
+        StartNewPage;
+      { Restore reference to lDsgnBand and SummaryBand, because StartNewPage
+        could have changed the value of lDsgnBand. }
+      lBand := TFPReportCustomBand(Report.FBands[I]);
+      CommonRuntimeBandProcessing(lBand);
+      UpdateSpaceRemaining(FRTBand);
+      end;
+    end;
 end;
 end;
 
 
-procedure TFPReportElementFactory.AssignReportElementTypes(AStrings: TStrings);
+procedure TFPReportLayouter.RecalcBandLayout(ABand: TFPReportCustomBand);
+
 var
 var
   i: integer;
   i: integer;
+  e: TFPReportElement;
+
 begin
 begin
-  AStrings.Clear;
-  for i := 0 to FList.Count - 1 do
-    AStrings.Add(Mappings[I].MappingName);
+  for i := ABand.ChildCount-1 downto 0 do
+  begin
+    e := ABand.Child[i];
+    if not e.EvaluateVisibility then
+    begin
+      ABand.RemoveChild(e);
+      FreeAndNil(e);
+    end;
+  end;
 end;
 end;
 
 
-{ TFPReportCustomDataHeaderBand }
+Procedure TFPReportLayouter.DoExecute;
 
 
-function TFPReportCustomDataHeaderBand.GetReportBandName: string;
-begin
-  Result := 'DataHeaderBand';
-end;
+Var
+  lPageIdx : Integer;
+  lPassIdx : Integer;
+  lPageData: TFPReportData;
+  aPassCount : Integer;
 
 
-class function TFPReportCustomDataHeaderBand.ReportBandType: TFPReportBandType;
 begin
 begin
-  Result:=btDataHeader;
+  if Report.TwoPass then
+    aPassCount := 2
+  else
+    aPassCount := 1;
+  // Pass loop
+  for lPassIdx := 1 to aPassCount do
+    begin
+    InitPass(lPassIdx);
+    // Design page loop
+    for lPageIdx := 0 to Report.PageCount-1 do
+      begin
+      lPageData:=Pages[lPageIdx].Data;
+      Report.FPageData:=lPagedata;
+      InitDesignPage(lPageIdx);
+      if Assigned(lPageData) then
+        RunDataLoop(lPageIdx,lPageData);
+      if FHasReportSummaryBand then
+        HandleReportSummaries;
+      end;
+    SetPageCount(RTObjects.Count);
+    end;
+  // DoProcessPass only substitutes cPageCountMarker by FPageCount.
+  // It is pointless to do so if we're doing 2 passes anyway
+  if Report.UsePageCountMarker then
+    Report.DoProcessTwoPass;
 end;
 end;
 
 
-{ TFPReportCustomDataFooterBand }
+procedure TFPReportLayouter.Execute(aReport: TFPCustomReport);
 
 
-function TFPReportCustomDataFooterBand.GetReportBandName: string;
 begin
 begin
-  Result := 'DataFooterBand';
+  FHeaderList := Nil;
+  FFooterList := Nil;
+  FmyReport:=AReport;
+  try
+    FHeaderList := TBandList.Create;
+    FFooterList := TBandList.Create;
+    DoExecute;
+  finally
+    FreeAndNil(FHeaderList);
+    FreeAndNil(FFooterList);
+    FMyReport:=Nil; // Don't free :)
+  end;
 end;
 end;
 
 
-class function TFPReportCustomDataFooterBand.ReportBandType: TFPReportBandType;
-begin
-  Result:=btDataFooter;
-end;
 
 
 { A function borrowed from fpGUI Toolkit. }
 { A function borrowed from fpGUI Toolkit. }
 function fpgDarker(const AColor: TFPReportColor; APercent: Byte): TFPReportColor;
 function fpgDarker(const AColor: TFPReportColor; APercent: Byte): TFPReportColor;