Browse Source

* Merging revisions r43889,r43891,r43895,r43899,r43900,r43930,r43954,r43955,r43973 from trunk:
------------------------------------------------------------------------
r43889 | michael | 2020-01-08 15:16:51 +0100 (Wed, 08 Jan 2020) | 1 line

* Reset parent font when needed. Patch by Pascal Riekenberg (bug ID 36531)
------------------------------------------------------------------------
r43891 | michael | 2020-01-08 20:37:48 +0100 (Wed, 08 Jan 2020) | 1 line

* Fix from Pascal Riekenberg to fix missing assign of columngap (bug ID 36537)
------------------------------------------------------------------------
r43895 | michael | 2020-01-09 10:52:25 +0100 (Thu, 09 Jan 2020) | 1 line

* Do not raise error in case of warning (bug ID 36541)
------------------------------------------------------------------------
r43899 | michael | 2020-01-10 14:35:01 +0100 (Fri, 10 Jan 2020) | 1 line

* Fix from Pascal Riekenberg to fix failing tests
------------------------------------------------------------------------
r43900 | michael | 2020-01-10 14:44:10 +0100 (Fri, 10 Jan 2020) | 1 line

* Patch from Pascal Riekenberg to allow saving rendered report to JSON (bug ID 36547)
------------------------------------------------------------------------
r43930 | michael | 2020-01-13 22:14:41 +0100 (Mon, 13 Jan 2020) | 1 line

* Fix from Pascal Riekenberg to fix nested groups (bug ID 36532)
------------------------------------------------------------------------
r43954 | michael | 2020-01-16 10:40:03 +0100 (Thu, 16 Jan 2020) | 1 line

* Patch from Pascal Riekenberg to fix running a report twice. (Bug ID 36592)
------------------------------------------------------------------------
r43955 | michael | 2020-01-16 11:27:16 +0100 (Thu, 16 Jan 2020) | 1 line

* Remove debug statement, patch by Pascal Riekenberg (Bug ID 36585)
------------------------------------------------------------------------
r43973 | michael | 2020-01-18 17:40:57 +0100 (Sat, 18 Jan 2020) | 1 line

* Fix bug #0036581, amended patch by Pascal Riekenberg
------------------------------------------------------------------------

git-svn-id: branches/fixes_3_2@43990 -

michael 5 years ago
parent
commit
611a60ad5b
31 changed files with 578 additions and 95 deletions
  1. 17 0
      .gitattributes
  2. 1 1
      packages/fcl-db/src/sqldb/oracle/oracleconnection.pp
  3. BIN
      packages/fcl-report/demos/fonts/DejaVuSans-Bold.ttf
  4. BIN
      packages/fcl-report/demos/fonts/DejaVuSans-BoldOblique.ttf
  5. BIN
      packages/fcl-report/demos/fonts/DejaVuSans-ExtraLight.ttf
  6. BIN
      packages/fcl-report/demos/fonts/DejaVuSans-Oblique.ttf
  7. BIN
      packages/fcl-report/demos/fonts/DejaVuSans.ttf
  8. BIN
      packages/fcl-report/demos/fonts/Ubuntu-B.ttf
  9. BIN
      packages/fcl-report/demos/fonts/Ubuntu-BI.ttf
  10. BIN
      packages/fcl-report/demos/fonts/Ubuntu-C.ttf
  11. BIN
      packages/fcl-report/demos/fonts/Ubuntu-L.ttf
  12. BIN
      packages/fcl-report/demos/fonts/Ubuntu-LI.ttf
  13. BIN
      packages/fcl-report/demos/fonts/Ubuntu-M.ttf
  14. BIN
      packages/fcl-report/demos/fonts/Ubuntu-MI.ttf
  15. BIN
      packages/fcl-report/demos/fonts/Ubuntu-R.ttf
  16. BIN
      packages/fcl-report/demos/fonts/Ubuntu-RI.ttf
  17. BIN
      packages/fcl-report/demos/fonts/Ubuntu-Th.ttf
  18. 4 0
      packages/fcl-report/demos/rptexpressions.pp
  19. 13 12
      packages/fcl-report/demos/rptgrouping.pp
  20. 32 31
      packages/fcl-report/demos/rptnestedgroups.pp
  21. 1 1
      packages/fcl-report/demos/rptttf.pp
  22. 13 6
      packages/fcl-report/demos/udapp.pp
  23. 50 0
      packages/fcl-report/src/fpjsonreport.pp
  24. 46 14
      packages/fcl-report/src/fpreport.pp
  25. 1 0
      packages/fcl-report/src/fpreportcontnr.pp
  26. 3 0
      packages/fcl-report/src/fpreportjson.pp
  27. 41 0
      packages/fcl-report/test/README.md
  28. 10 1
      packages/fcl-report/test/regtests.pp
  29. 32 28
      packages/fcl-report/test/tcbasereport.pp
  30. 313 0
      packages/fcl-report/test/tcreportgenerator.pas
  31. 1 1
      packages/fcl-report/test/testfpreport.lpi

+ 17 - 0
.gitattributes

@@ -2700,6 +2700,11 @@ packages/fcl-report/demos/countries2.inc svneol=native#text/plain
 packages/fcl-report/demos/demos.inc svneol=native#text/plain
 packages/fcl-report/demos/fcldemo.lpi svneol=native#text/plain
 packages/fcl-report/demos/fcldemo.pp svneol=native#text/plain
+packages/fcl-report/demos/fonts/DejaVuSans-Bold.ttf -text
+packages/fcl-report/demos/fonts/DejaVuSans-BoldOblique.ttf -text
+packages/fcl-report/demos/fonts/DejaVuSans-ExtraLight.ttf -text
+packages/fcl-report/demos/fonts/DejaVuSans-Oblique.ttf -text
+packages/fcl-report/demos/fonts/DejaVuSans.ttf -text
 packages/fcl-report/demos/fonts/LiberationSans-Bold.ttf -text
 packages/fcl-report/demos/fonts/LiberationSans-BoldItalic.ttf -text
 packages/fcl-report/demos/fonts/LiberationSans-Italic.ttf -text
@@ -2708,6 +2713,16 @@ packages/fcl-report/demos/fonts/LiberationSerif-Bold.ttf -text
 packages/fcl-report/demos/fonts/LiberationSerif-BoldItalic.ttf -text
 packages/fcl-report/demos/fonts/LiberationSerif-Italic.ttf -text
 packages/fcl-report/demos/fonts/LiberationSerif-Regular.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-B.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-BI.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-C.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-L.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-LI.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-M.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-MI.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-R.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-RI.ttf -text
+packages/fcl-report/demos/fonts/Ubuntu-Th.ttf -text
 packages/fcl-report/demos/laz2fpreport.lpi svneol=native#text/plain
 packages/fcl-report/demos/laz2fpreport.pp svneol=native#text/plain
 packages/fcl-report/demos/pictures/man01.png -text svneol=unset#image/png
@@ -2776,6 +2791,7 @@ packages/fcl-report/src/fpreportjson.pp svneol=native#text/plain
 packages/fcl-report/src/fpreportpdfexport.pp svneol=native#text/plain
 packages/fcl-report/src/fpreportqrcode.pp svneol=native#text/plain
 packages/fcl-report/src/fpreportstreamer.pp svneol=native#text/plain
+packages/fcl-report/test/README.md svneol=native#text/plain
 packages/fcl-report/test/fonts/LiberationSerif-Regular.ttf -text
 packages/fcl-report/test/fonts/calibri.ttf -text
 packages/fcl-report/test/fonts/calibrib.ttf -text
@@ -2787,6 +2803,7 @@ packages/fcl-report/test/regtests.pp svneol=native#text/plain
 packages/fcl-report/test/tcbasereport.pp svneol=native#text/plain
 packages/fcl-report/test/tchtmlparser.pas svneol=native#text/plain
 packages/fcl-report/test/tcreportdom.pp svneol=native#text/plain
+packages/fcl-report/test/tcreportgenerator.pas svneol=native#text/plain
 packages/fcl-report/test/tcreportstreamer.pp svneol=native#text/plain
 packages/fcl-report/test/testfpreport.lpi svneol=native#text/plain
 packages/fcl-report/test/testfpreport.lpr svneol=native#text/plain

+ 1 - 1
packages/fcl-db/src/sqldb/oracle/oracleconnection.pp

@@ -1051,7 +1051,7 @@ begin
     OCI_SUCCESS : Result := True;
     OCI_SUCCESS_WITH_INFO : Begin
                             Result := True;
-                            HandleError;
+                            // HandleError;
                             end;
   end; {case}
 end;

BIN
packages/fcl-report/demos/fonts/DejaVuSans-Bold.ttf


BIN
packages/fcl-report/demos/fonts/DejaVuSans-BoldOblique.ttf


BIN
packages/fcl-report/demos/fonts/DejaVuSans-ExtraLight.ttf


BIN
packages/fcl-report/demos/fonts/DejaVuSans-Oblique.ttf


BIN
packages/fcl-report/demos/fonts/DejaVuSans.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-B.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-BI.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-C.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-L.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-LI.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-M.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-MI.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-R.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-RI.ttf


BIN
packages/fcl-report/demos/fonts/Ubuntu-Th.ttf


+ 4 - 0
packages/fcl-report/demos/rptexpressions.pp

@@ -135,7 +135,11 @@ begin
   Memo.Layout.Top := 20;
   Memo.Layout.Width := 50;
   Memo.Layout.Height := 8;
+  {$IFDEF fptestX}
+  Memo.Text := 'Report Date: 2020-01-15';
+  {$ELSE}
   Memo.Text := 'Report Date: [TODAY]';
+  {$ENDIF}
 
   Memo := TFPReportMemo.Create(TitleBand);
   Memo.Layout.Left := 0;

+ 13 - 12
packages/fcl-report/demos/rptgrouping.pp

@@ -154,7 +154,7 @@ begin
   {*** group header ***}
   GroupHeader := TFPReportGroupHeaderBand.Create(p);
   GroupHeader.Layout.Height := 15;
-  GroupHeader.GroupCondition := 'copy(country,1,1)';
+  GroupHeader.GroupCondition := 'copy(data.country,1,1)';
   {$ifdef ColorBands}
   GroupHeader.Frame.Shape := fsRectangle;
   GroupHeader.Frame.BackgroundColor := clGroupHeaderFooter;
@@ -166,7 +166,7 @@ begin
   Memo.Layout.Width := 10;
   Memo.Layout.Height := 8;
   Memo.UseParentFont := False;
-  Memo.Text := '[copy(country,1,1)]';
+  Memo.Text := '[copy(data.country,1,1)]';
   Memo.Font.Size := 16;
 
   Memo := TFPReportMemo.Create(GroupHeader);
@@ -197,11 +197,11 @@ begin
 
 
   {*** variables ***}
-  rpt.Variables.AddExprVariable('population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, rtNone, '');
-  rpt.Variables.AddExprVariable('grp_sum_population', 'sum(StrToFloat(population))',rtFloat , GroupHeader);
-  rpt.Variables.AddExprVariable('grp_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, GroupHeader);
-  rpt.Variables.AddExprVariable('sum_population', 'sum(StrToFloat(population))', rtFloat, rtnone, '');
-  rpt.Variables.AddExprVariable('sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat,rtnone,'');
+  rpt.Variables.AddExprVariable('population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat, rtNone, '');
+  rpt.Variables.AddExprVariable('grp_sum_population', 'sum(StrToFloat(data.population))',rtFloat , GroupHeader);
+  rpt.Variables.AddExprVariable('grp_sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat, GroupHeader);
+  rpt.Variables.AddExprVariable('sum_population', 'sum(StrToFloat(data.population))', rtFloat, rtnone, '');
+  rpt.Variables.AddExprVariable('sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat,rtnone,'');
 
 
   {*** detail ***}
@@ -218,7 +218,7 @@ begin
   Memo.Layout.Top := 2;
   Memo.Layout.Width := 45;
   Memo.Layout.Height := 5;
-  Memo.Text := '[country]';
+  Memo.Text := '[data.country]';
 
   Memo := TFPReportMemo.Create(DataBand);
   Memo.Layout.Left := 55;
@@ -237,7 +237,7 @@ begin
   Memo.Text := '> Germany';
   Memo.UseParentFont := false;
   Memo.Font.Color := clGreen;
-  Memo.VisibleExpr := 'StrToFloat(population) > 80890000';
+  Memo.VisibleExpr := 'StrToFloat(data.population) > 80890000';
 
   Memo := TFPReportMemo.Create(DataBand);
   Memo.Layout.Left := 85;
@@ -247,7 +247,7 @@ begin
   Memo.Text := '< Germany';
   Memo.UseParentFont := false;
   Memo.Font.Color := clRed;
-  Memo.VisibleExpr := 'StrToFloat(population) < 80890000';
+  Memo.VisibleExpr := 'StrToFloat(data.population) < 80890000';
 
   Memo := TFPReportMemo.Create(DataBand);
   Memo.Layout.Left := 110;
@@ -255,7 +255,7 @@ begin
   Memo.Layout.Width := 15;
   Memo.Layout.Height := 5;
   Memo.TextAlignment.Horizontal := taRightJustified;
-  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp_sum_population*100)] %';
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/grp_sum_population*100)] %';
 
   Memo := TFPReportMemo.Create(DataBand);
   Memo.Layout.Left := 130;
@@ -263,7 +263,7 @@ begin
   Memo.Layout.Width := 15;
   Memo.Layout.Height := 5;
   Memo.TextAlignment.Horizontal := taRightJustified;
-  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/sum_population*100)] %';
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/sum_population*100)] %';
 
   {*** group footer ***}
   GroupFooter := TFPReportGroupFooterBand.Create(p);
@@ -353,6 +353,7 @@ constructor TGroupingDemo.Create(AOwner: TComponent);
 begin
   inherited;
   lReportData := TFPReportUserData.Create(nil);
+  lReportData.Name := 'data';
   lReportData.OnGetValue := @GetReportDataValue;
   lReportData.OnGetEOF := @GetReportDataEOF;
   lReportData.OnFirst := @GetReportDataFirst;

+ 32 - 31
packages/fcl-report/demos/rptnestedgroups.pp

@@ -192,7 +192,7 @@ begin
 
   GroupHeader1Region := TFPReportGroupHeaderBand.Create(Page);
   GroupHeader1Region.Layout.Height := 15;
-  GroupHeader1Region.GroupCondition := 'region';
+  GroupHeader1Region.GroupCondition := 'data.region';
   GroupHeader1Region.Frame.Shape := fsRectangle;
   GroupHeader1Region.Frame.BackgroundColor := clGroupHeaderFooter;
   //GroupHeader1Region.StartOnNewPage := True;
@@ -209,7 +209,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 16;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := 'Region: [region] ([formatfloat(''#,##0.0'', grp1region_sum_population_in_M)] M)';
+    Memo.Text := 'Region: [data.region] ([formatfloat(''#,##0.0'', grp1region_sum_population_in_M)] M)';
 
     Memo := TFPReportMemo.Create(GroupHeader1Region);
     Memo.Layout.Left := 25;
@@ -235,7 +235,7 @@ begin
     Memo.Layout.Top := 1;
     Memo.Layout.Width := 170;
     Memo.Layout.Height := 4;
-    Memo.Text := 'Region: [region]';
+    Memo.Text := 'Region: [data.region]';
 
 
   {*** group header 2 subregion ***}
@@ -244,7 +244,7 @@ begin
 
   GroupHeader2Subregion := TFPReportGroupHeaderBand.Create(Page);
   GroupHeader2Subregion.Layout.Height := 2;
-  GroupHeader2Subregion.GroupCondition := 'subregion';
+  GroupHeader2Subregion.GroupCondition := 'data.subregion';
   GroupHeader2Subregion.ParentGroupHeader := GroupHeader1Region;
   //GroupHeader2Subregion.StartOnNewPage := True;
   GroupHeader2Subregion.ReprintedHeader := [rsPage];
@@ -295,7 +295,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 14;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := 'Subregion: [subregion] ([formatfloat(''#,##0.0'', grp2subregion_sum_population_in_M)] M)';
+    Memo.Text := 'Subregion: [data.subregion] ([formatfloat(''#,##0.0'', grp2subregion_sum_population_in_M)] M)';
 
     Memo := TFPReportMemo.Create(ChildBand);
     Memo.Layout.Left := 25;
@@ -306,7 +306,7 @@ begin
     Memo.Font.Size := 10;
     Memo.TextAlignment.Vertical := tlBottom;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#0.0'', grp2subregion_sum_population / grp1region_sum_population * 100)] % in [region] - [formatfloat(''#0.0'', grp2subregion_sum_population / total_sum_population * 100)] % in World';
+    Memo.Text := '[formatfloat(''#0.0'', grp2subregion_sum_population / grp1region_sum_population * 100)] % in [data.region] - [formatfloat(''#0.0'', grp2subregion_sum_population / total_sum_population * 100)] % in World';
 
   {--- group header 2 subregion - band 3 ---}
 
@@ -340,7 +340,7 @@ begin
     Memo.Layout.Top := 1;
     Memo.Layout.Width := 170;
     Memo.Layout.Height := 4;
-    Memo.Text := 'Subregion: [subregion]';
+    Memo.Text := 'Subregion: [data.subregion]';
 
 
   {*** group header 3 initial ***}
@@ -349,7 +349,7 @@ begin
 
   GroupHeader3Initial := TFPReportGroupHeaderBand.Create(Page);
   GroupHeader3Initial.Layout.Height := 2;
-  GroupHeader3Initial.GroupCondition := 'copy(country,1,1)';
+  GroupHeader3Initial.GroupCondition := 'copy(data.country,1,1)';
   GroupHeader3Initial.ParentGroupHeader := GroupHeader2Subregion;
   GroupHeader3Initial.ReprintedHeader := [rsPage];
   GroupHeader3Initial.IntermediateFooter := [rsPage];
@@ -426,7 +426,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 12;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := '[copy(country,1,1)]  ([formatfloat(''#,##0.0'', grp3initial_sum_population_in_M)] M)';
+    Memo.Text := '[copy(data.country,1,1)]  ([formatfloat(''#,##0.0'', grp3initial_sum_population_in_M)] M)';
 
     Memo := TFPReportMemo.Create(ChildBand);
     Memo.Layout.Left := 25;
@@ -437,7 +437,7 @@ begin
     Memo.Font.Size := 10;
     Memo.TextAlignment.Vertical := tlBottom;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#0.0'', grp3initial_sum_population / grp2subregion_sum_population * 100)] % in [subregion] - [formatfloat(''#0.0'', grp3initial_sum_population / grp1region_sum_population * 100)] % in [region] - [formatfloat(''#0.0'', grp3initial_sum_population / total_sum_population * 100)] % in World';
+    Memo.Text := '[formatfloat(''#0.0'', grp3initial_sum_population / grp2subregion_sum_population * 100)] % in [data.subregion] - [formatfloat(''#0.0'', grp3initial_sum_population / grp1region_sum_population * 100)] % in [data.region] - [formatfloat(''#0.0'', grp3initial_sum_population / total_sum_population * 100)] % in World';
 
     Memo := TFPReportMemo.Create(ChildBand);
     Memo.Layout.Left := 90;
@@ -521,7 +521,7 @@ begin
     Memo.Layout.Top := 1;
     Memo.Layout.Width := 170;
     Memo.Layout.Height := 4;
-    Memo.Text := '[copy(country,1,1)]';
+    Memo.Text := '[copy(data.country,1,1)]';
 
 
   {--- group header 3 initial - band 4 ---}
@@ -559,15 +559,15 @@ begin
 
   {*** variables ***}
 
-  rpt.Variables.AddExprVariable('population_in_M', 'StrToFloat(population) / 1000000', rtFloat, rtNone, '');
-  rpt.Variables.AddExprVariable('grp1region_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, GroupHeader1Region);
-  rpt.Variables.AddExprVariable('grp1region_sum_population', 'sum(StrToFloat(population))', rtFloat, GroupHeader1Region);
-  rpt.Variables.AddExprVariable('grp2subregion_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, GroupHeader2Subregion);
-  rpt.Variables.AddExprVariable('grp2subregion_sum_population', 'sum(StrToFloat(population))', rtFloat, GroupHeader2Subregion);
-  rpt.Variables.AddExprVariable('grp3initial_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, GroupHeader3Initial);
-  rpt.Variables.AddExprVariable('grp3initial_sum_population', 'sum(StrToFloat(population))', rtFloat, GroupHeader3Initial);
-  rpt.Variables.AddExprVariable('total_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat);
-  rpt.Variables.AddExprVariable('total_sum_population', 'sum(StrToFloat(population))', rtFloat);
+  rpt.Variables.AddExprVariable('population_in_M', 'StrToFloat(data.population) / 1000000', rtFloat, rtNone, '');
+  rpt.Variables.AddExprVariable('grp1region_sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat, GroupHeader1Region);
+  rpt.Variables.AddExprVariable('grp1region_sum_population', 'sum(StrToFloat(data.population))', rtFloat, GroupHeader1Region);
+  rpt.Variables.AddExprVariable('grp2subregion_sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat, GroupHeader2Subregion);
+  rpt.Variables.AddExprVariable('grp2subregion_sum_population', 'sum(StrToFloat(data.population))', rtFloat, GroupHeader2Subregion);
+  rpt.Variables.AddExprVariable('grp3initial_sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat, GroupHeader3Initial);
+  rpt.Variables.AddExprVariable('grp3initial_sum_population', 'sum(StrToFloat(data.population))', rtFloat, GroupHeader3Initial);
+  rpt.Variables.AddExprVariable('total_sum_population_in_M', 'sum(StrToFloat(data.population) / 1000000)', rtFloat);
+  rpt.Variables.AddExprVariable('total_sum_population', 'sum(StrToFloat(data.population))', rtFloat);
 
 
   {****************}
@@ -639,7 +639,7 @@ begin
     Memo.Layout.Top := 2;
     Memo.Layout.Width := 45;
     Memo.Layout.Height := 5;
-    Memo.Text := '[country]';
+    Memo.Text := '[data.country]';
     Memo.Options := memo.Options + [moDisableWordWrap];
 
     Memo := TFPReportMemo.Create(DataBand);
@@ -648,7 +648,7 @@ begin
     Memo.Layout.Width := 25;
     Memo.Layout.Height := 5;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+    Memo.Text := '[formatfloat(''#,##0'', StrToFloat(data.population))]';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 80;
@@ -658,7 +658,7 @@ begin
     Memo.Text := '> DEU';
     Memo.UseParentFont := false;
     Memo.Font.Color := clGreen;
-    Memo.VisibleExpr := 'StrToFloat(population) > 82667685';
+    Memo.VisibleExpr := 'StrToFloat(data.population) > 82667685';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 80;
@@ -668,7 +668,7 @@ begin
     Memo.Text := '< DEU';
     Memo.UseParentFont := false;
     Memo.Font.Color := clRed;
-    Memo.VisibleExpr := 'StrToFloat(population) < 82667685';
+    Memo.VisibleExpr := 'StrToFloat(data.population) < 82667685';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 95;
@@ -676,7 +676,7 @@ begin
     Memo.Layout.Width := 15;
     Memo.Layout.Height := 5;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp3initial_sum_population*100)] %';
+    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/grp3initial_sum_population*100)] %';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 115;
@@ -684,7 +684,7 @@ begin
     Memo.Layout.Width := 15;
     Memo.Layout.Height := 5;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp2subregion_sum_population*100)] %';
+    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/grp2subregion_sum_population*100)] %';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 135;
@@ -692,7 +692,7 @@ begin
     Memo.Layout.Width := 15;
     Memo.Layout.Height := 5;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp1region_sum_population*100)] %';
+    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/grp1region_sum_population*100)] %';
 
     Memo := TFPReportMemo.Create(DataBand);
     Memo.Layout.Left := 155;
@@ -700,7 +700,7 @@ begin
     Memo.Layout.Width := 15;
     Memo.Layout.Height := 5;
     Memo.TextAlignment.Horizontal := taRightJustified;
-    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/total_sum_population*100)] %';
+    Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(data.population)/total_sum_population*100)] %';
 
 
   {**********************}
@@ -821,7 +821,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 12;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := 'Population [copy(country,1,1)]: [formatfloat(''#,##0'', grp3initial_sum_population)]';
+    Memo.Text := 'Population [copy(data.country,1,1)]: [formatfloat(''#,##0'', grp3initial_sum_population)]';
 
   {--- group footer 3 initial - band 4 ---}
 
@@ -947,7 +947,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 14;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := 'Population [subregion]: [formatfloat(''#,##0'', grp2subregion_sum_population)]';
+    Memo.Text := 'Population [data.subregion]: [formatfloat(''#,##0'', grp2subregion_sum_population)]';
 
   {--- group footer 2 subregion - band 4 ---}
 
@@ -1038,7 +1038,7 @@ begin
     Memo.UseParentFont := False;
     Memo.Font.Size := 16;
     Memo.TextAlignment.Vertical := tlBottom;
-    Memo.Text := 'Population [region]: [formatfloat(''#,##0'', grp1region_sum_population)]';
+    Memo.Text := 'Population [data.region]: [formatfloat(''#,##0'', grp1region_sum_population)]';
 
   {--- group footer 1 region - band 3 ---}
 
@@ -1182,6 +1182,7 @@ constructor TNestedGroupsDemo.Create(AOWner: TComponent);
 begin
   inherited;
   FReportData := TFPReportUserData.Create(nil);
+  FReportData.Name := 'Data';
   FReportData.OnGetValue := @GetReportDataValue;
   FReportData.OnGetEOF := @GetReportDataEOF;
   FReportData.OnFirst := @GetReportDataFirst;

+ 1 - 1
packages/fcl-report/demos/rptttf.pp

@@ -153,7 +153,7 @@ begin
   GroupHeader := TFPReportGroupHeaderBand.Create(p);
   GroupHeader.Layout.Height := 15;
   GroupHeader.Data := lReportData;
-  GroupHeader.GroupCondition := '[copy(country,1,1)]';
+  GroupHeader.GroupCondition := 'copy(country,1,1)';
   GroupHeader.Frame.BackgroundColor := clYellow;    // this has no affect on rendered PDF because here Shape = fsNone
   GroupHeader.Frame.Color :=   TFPReportColor($01579B);
   GroupHeader.Frame.Lines := [flBottom];

+ 13 - 6
packages/fcl-report/demos/udapp.pp

@@ -7,7 +7,7 @@ unit udapp;
 interface
 
 uses
-  Classes, SysUtils, fpttf, fpreport,
+  Classes, SysUtils, fpttf, fpreport, fpjsonreport,
 
   {$IFDEF ExportPDF}
   fpreportpdfexport,
@@ -48,14 +48,15 @@ Type
 
   TReportDemoApp = class(TComponent)
   private
-    Frpt: TFPReport;
+    Frpt: TFPJSONReport;
   protected
     procedure InitialiseData; virtual;
     procedure CreateReportDesign; virtual;
   public
+    procedure TestInit;
     Class Function Description : string; virtual;
 //    procedure DoCreateJSON(const AFileName: String; RunTime: Boolean=False);
-    Property rpt : TFPReport read Frpt Write FRpt;
+    Property rpt : TFPJSONReport read Frpt Write FRpt;
   end;
   TReportDemoAppClass = Class of TReportDemoApp;
 
@@ -144,14 +145,20 @@ begin
     PaperManager.RegisterStandardSizes;
 end;
 
+procedure TReportDemoApp.TestInit;
+begin
+  Frpt := TFPJSONReport.Create(Self);
+  InitialiseData;
+  CreateReportDesign;
+end;
+
 class function TReportDemoApp.Description: string;
 begin
   Result:='';
 end;
 
 
-class function TReportDemoApplication.GetRenderClass(F: TRenderFormat
-  ): TFPReportExporterClass;
+class function TReportDemoApplication.GetRenderClass(F: TRenderFormat): TFPReportExporterClass;
 
 begin
   Case F of
@@ -465,7 +472,7 @@ begin
   if (F<>'') and (CompareText(F,'default')<>0) and (Fmt=rfDefault) then
     Usage(Format('Unknown output format: %s',[F]));
   FRunner.ReportApp:=GetReportClass(D).Create(Self);
-  FRunner.ReportApp.rpt:=TFPReport.Create(FRunner.ReportApp);
+  FRunner.ReportApp.rpt:=TFPJSONReport.Create(FRunner.ReportApp);
   FRunner.Format:=Fmt;
   FRunner.DesignFileName:=J;
   FRunner.Execute;

+ 50 - 0
packages/fcl-report/src/fpjsonreport.pp

@@ -52,10 +52,13 @@ Type
     destructor Destroy; override;
     procedure LoadFromStream(const aStream: TStream);
     procedure SaveToStream(const aStream: TStream);
+    procedure SaveRenderToStream(const aStream: TStream);
     Procedure LoadFromJSON(aJSON : TJSONObject); virtual;
     Procedure SavetoJSON(aJSON : TJSONObject); virtual;
+    Procedure SaveRenderToJSON(aJSON : TJSONObject); virtual;
     Procedure LoadFromFile(const aFileName : String);
     Procedure SaveToFile(const aFileName : String);
+    procedure SaveRenderToFile(const aFileName: String);
     Property LoadErrors : TStrings Read FLoadErrors;
     Property DataManager : TFPCustomReportDataManager Read FDataManager Write SetDataManager;
     Property DesignDataName : String Read GetDesignDataName Write SetDesignDataName Stored StoreDesignDataName;
@@ -242,6 +245,23 @@ begin
   end;
 end;
 
+procedure TFPJSONReport.SaveRenderToJSON(aJSON: TJSONObject);
+
+Var
+  R : TFPReportJSONStreamer;
+
+begin
+  DoWriteJSON(aJSON);
+  R:=TFPReportJSONStreamer.Create(Nil);
+  try
+    R.OwnsJSON:=False;
+    R.JSON:=aJSON;
+    WriteRTElement(R);
+  finally
+    R.Free;
+  end;
+end;
+
 procedure TFPJSONReport.LoadFromStream(const aStream : TStream);
 
 Var
@@ -275,6 +295,23 @@ begin
   end;
 end;
 
+procedure TFPJSONReport.SaveRenderToStream(const aStream: TStream);
+
+Var
+  O : TJSONObject;
+  S : TJSONStringType;
+
+begin
+  O:=TJSONObject.Create;
+  try
+    SaveRendertoJSON(O);
+    S:=O.AsJSON;
+    aStream.WriteBuffer(S[1],Length(S));
+  finally
+    O.Free;
+  end;
+end;
+
 procedure TFPJSONReport.LoadFromFile(const aFileName: String);
 
 Var
@@ -302,5 +339,18 @@ begin
   end;
 end;
 
+procedure TFPJSONReport.SaveRenderToFile(const aFileName: String);
+Var
+  F : TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmCreate);
+  try
+    SaveRenderToStream(F);
+  finally
+    F.Free;
+  end;
+end;
+
 end.
 

+ 46 - 14
packages/fcl-report/src/fpreport.pp

@@ -22,7 +22,7 @@ unit fpreport;
 // Global debugging
 { $define gdebug}
 // Separate for aggregate variables
-{$define gdebuga}
+{ $define gdebuga}
 
 interface
 
@@ -1676,6 +1676,7 @@ type
     Procedure SaveDataToNames;
     Procedure RestoreDataFromNames;
     procedure WriteElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
+    procedure WriteRTElement(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil);
     procedure ReadElement(AReader: TFPReportStreamer); override;
     procedure AddPage(APage: TFPReportCustomPage);
     procedure RemovePage(APage: TFPReportCustomPage);
@@ -3305,9 +3306,9 @@ begin
   if (aNode is TFPExprVariable) then
     begin
     DS:=ExtractWord(1,TFPExprVariable(ANode).Identifier.Name,['.']);
-    If AData.FindReportData(DS)<>Nil then
+      If AData.FindReportData(DS)<>Nil then
       FDataName:=DS;
-    end
+      end
   else if (ANode is TFPExprFunction) then
     begin
     I:=0;
@@ -3512,11 +3513,7 @@ begin
   if Not SameText(aData.Name,FDataName) then
     exit;
   If not IsFirstPass then
-    begin
-    FLastValue.ResultType:=rtFloat;
-    FLastValue.ResFloat:=0;
     exit;
-    end;
   if (FResetValue=#0) then
     begin
     FResetValue:=#255;
@@ -3623,8 +3620,8 @@ begin
         inc(FAggregateValuesIndex);
         end;
       FResetValue:=lResetValue;
-      FAggregateValue:=PFPExpressionResult(FAggregateValues[FAggregateValuesIndex])^;
       FLastValue:=FAggregateValue;
+      FAggregateValue:=PFPExpressionResult(FAggregateValues[FAggregateValuesIndex])^;
       end
     else
       begin
@@ -3643,7 +3640,7 @@ begin
     begin
     WriteString('Name',Self.Name);
     WriteString('DataType',ResultTypeName(DataType));
-    WriteString('Value',Value);
+//    WriteString('Value',Value);
     WriteString('Expression',Expression);
     WriteString('ResetValueExpression',ResetValueExpression);
     WriteString('ResetType',GetEnumName(TypeInfo(TFPReportResetType),Ord(ResetType)));
@@ -3668,7 +3665,7 @@ begin
       DataType:=rtString
     else
       DataType:=TResultType(I);
-    Value:=ReadString('Value','');
+//    Value:=ReadString('Value','');
     Expression:=ReadString('Expression','');
     ResetValueExpression:=ReadString('ResetValueExpression','');
     S:=ReadString('ResetType','');
@@ -5137,6 +5134,7 @@ begin
   FUseParentFont := True;
   FFont := TFPReportFont.Create;
   FFont.OnChanged:=@HandleFontChange;
+  ReassignParentFont;
   FCullThreshold := 75;
 end;
 
@@ -7733,6 +7731,7 @@ begin
     Report := E.Report;
     Font.Assign(E.Font);
     ColumnCount := E.ColumnCount;
+    ColumnGap := E.ColumnGap;
   end;
   inherited Assign(Source);
 end;
@@ -8724,6 +8723,39 @@ begin
   // TODO: Implement writing OnRenderReport, OnBeginReport, OnEndReport
 end;
 
+procedure TFPCustomReport.WriteRTElement(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.WriteBoolean('TwoPass',TwoPass);
+    AWriter.WriteDateTime('DateCreated', DateCreated);
+    // now the pages
+    AWriter.PushElement('Pages');
+    try
+      for i := 0 to RTObjects.Count - 1 do
+      begin
+        AWriter.PushElement(IntToStr(i)); // use page index as identifier
+        try
+          TFPReportComponent(RTObjects[i]).WriteElement(AWriter);
+        finally
+          AWriter.PopElement;
+        end;
+      end;
+    finally
+      AWriter.PopElement;
+    end;
+  finally
+    AWriter.PopElement;
+  end;
+end;
+
 procedure TFPCustomReport.ReadElement(AReader: TFPReportStreamer);
 var
   E: TObject;
@@ -8927,6 +8959,7 @@ end;
 procedure TFPCustomReport.RunReport;
 begin
   DoBeginReport;
+  ClearPreparedReport;
   StartLayout;
   CollectReportData;
   Validate;
@@ -9439,6 +9472,7 @@ begin
   FBandPosition := bpNormal;
   FFont:=TFPReportFont.Create;
   FFont.OnChanged:=@HandleFontChange;
+  ReassignParentFont;
 end;
 
 destructor TFPReportCustomBand.Destroy;
@@ -11895,12 +11929,11 @@ begin
         {$endif}
         // DumpData(lData);
         PrepareRecord(lData);
+        Report.UpdateAggregates(lPage,lData);
         if FNewPage then
           StartNewPage;
         ShowDataHeaderBand;
         HandleGroupBands;
-        // This must be done after the groups were handled.
-        Report.UpdateAggregates(lPage,lData);
         ShowDataBand;
         lData.Next;
         end;  { while not lData.EOF }
@@ -12096,12 +12129,11 @@ begin
         {$endif}
         // DumpData(aPageData);
         PrepareRecord(aData);
+        Report.UpdateAggregates(aPage,aData);
         if FNewPage then
           StartNewPage;
         ShowDataHeaderBand;
         HandleGroupBands;
-        // This must be done after the groups were handled.
-        Report.UpdateAggregates(aPage,aData);
         ShowDataBand;
         aData.Next;
         end;

+ 1 - 0
packages/fcl-report/src/fpreportcontnr.pp

@@ -260,6 +260,7 @@ procedure TFPReportObjectData.DoClose;
 begin
   FIndex:=-1;
   inherited DoClose;
+  DataFields.Clear;
 end;
 
 function TFPReportObjectData.DoEOF: boolean;

+ 3 - 0
packages/fcl-report/src/fpreportjson.pp

@@ -190,7 +190,9 @@ begin
   else
     AValue:=D.AsJSON;
   end;
+  {$IFDEF gdebug}
   Writeln(FIndex,' : ',AFieldName,' -> ',AValue);
+  {$ENDIF}
 end;
 
 procedure TFPReportJSONData.DoInitDataFields;
@@ -249,6 +251,7 @@ end;
 procedure TFPReportJSONData.DoClose;
 begin
   inherited DoClose;
+  DataFields.Clear;
   FIndex:=-1;
 end;
 

+ 41 - 0
packages/fcl-report/test/README.md

@@ -0,0 +1,41 @@
+# Testsuite
+
+## Demos
+
+The testsuite can optionally run all demos: define USEDEMOS and fpTestX on the
+command-line or in the lazarus defines.
+
+In that case the ../demo and ../demo/polygon directories must be added to
+the unit path of the compiler.
+
+You can then run these tests using the following command-line
+./testfpreport --suite=TTestDemos
+
+The demo reports will be rendered and saved to a directory "rendered".
+
+The first time you run the demo test, the file will be called demo.set.json.
+The second time you run the demo test, if the result differs, the result
+will be saved to a file called demo.actual.json.
+
+So, to test changes, first delete all json files in  the rendered directory.
+Then do a first run, this will create the initial files, and set a baseline. 
+Make your changes, and then run the reports again. You will be notified of
+differences.
+
+The reason these files are not stored in SVN is that they are dependent on
+the platform:
+a) The reports contain newlines. 
+   Depending on the platform they will be saved as \r \r\n or \n. 
+b) There can and will be localization issues. 
+
+## Fonts 
+
+The demos need some extra fonts. 
+The needed fonts (Ubuntu and DejaVu Sans) can be downloaded from:
+
+* https://assets.ubuntu.com/v1/0cef8205-ubuntu-font-family-0.83.zip
+* https://www.downloadfonts.io/calibri-font-family-free/
+* https://www.fontsquirrel.com/fonts/download/dejavu-sans
+* https://www.fontsquirrel.com/fonts/download/liberation-sans
+
+These fonts should be saved to the ./fonts or ../demo/fonts directory.

+ 10 - 1
packages/fcl-report/test/regtests.pp

@@ -4,10 +4,19 @@ unit regtests;
   Add all test units to the uses clause here.
   Avoids messing with the uses clause of the main program(s).
 }
+
+// Define USEDEMOS if you want to test & compare rendering of the demos.
+
+{$DEFINE USEDEMOS}
+
 interface
 
 uses
-  tcbasereport, tcreportstreamer, tchtmlparser;
+  tcbasereport, tcreportstreamer, tchtmlparser
+{$IFDEF USEDEMOS}
+  , tcreportgenerator
+{$ENDIF}
+  ;
 
 implementation
   

+ 32 - 28
packages/fcl-report/test/tcbasereport.pp

@@ -239,7 +239,7 @@ type
 
   TTestReportChildren = class(TTestCase)
   private
-    FC: TMyFPReportElementWithChildren;
+    FC, FC2: TMyFPReportElementWithChildren;
     FChild: TFPReportElement;
   protected
     procedure SetUp; override;
@@ -743,11 +743,11 @@ begin
   Variable.DataType:=rtFloat;
   AssertEquals('Float type remains',rtFloat,Variable.DataType);
   AssertEquals('Float default value',0.0,Variable.AsFloat);
-  AssertEquals('Float as string',0.0,StrToFloat(Variable.Value));
+  AssertEquals('Float as string',' 0.0000000000000000E+000',Variable.Value);
   Variable.DataType:=rtBoolean;
   Variable.AsFloat:=1.23;
   AssertEquals('Float type remains',rtFloat,Variable.DataType);
-  AssertEquals('Float as string',1.23,StrToFloat(Variable.Value));
+  AssertEquals('Float as string',' 1.2300000000000000E+000',Variable.Value);
   AssertEquals('Float value',1.23,Variable.AsFloat);
   R:=Variable.AsExpressionResult;
   AssertEquals('Correct result',rtFloat,r.resulttype);
@@ -1520,13 +1520,15 @@ end;
 procedure TTestReportChildren.SetUp;
 begin
   FC := TMyFPReportElementWithChildren.Create(nil);
+  FC2 := TMyFPReportElementWithChildren.Create(nil);
   FChild := TFPReportElement.Create(nil);
 end;
 
 procedure TTestReportChildren.TearDown;
 begin
-  FreeAndNil(FC);
   FreeAndNil(FChild);
+  FreeAndNil(FC);
+  FreeAndNil(FC2);
 end;
 
 procedure TTestReportChildren.WrongParent;
@@ -1562,17 +1564,12 @@ begin
   AssertEquals('Parent childcount is 1', 1, FC.ChildCount);
   AssertSame('Parent first child is OK', FChild, FC.Child[0]);
   FC.ResetChanged;
-  E := TFPReportElementWithChildren.Create(nil);
-  try
-    FChild.Parent := E;
-    AssertSame('Parent was saved', E, FChild.parent);
-    AssertEquals('Changed was called', 1, FC.ChangedCalled);
-    AssertEquals('Old Parent childcount is 0', 0, FC.ChildCount);
-    AssertEquals('Parent childcount is 1', 1, E.ChildCount);
-    AssertSame('Parent first child is OK', FChild, E.Child[0]);
-  finally
-    E.Free;
-  end;
+  FChild.Parent := FC2;
+  AssertSame('Parent was saved', FC2, FChild.parent);
+  AssertEquals('Changed was called', 1, FC.ChangedCalled);
+  AssertEquals('Old Parent childcount is 0', 0, FC.ChildCount);
+  AssertEquals('Parent childcount is 1', 1, FC2.ChildCount);
+  AssertSame('Parent first child is OK', FChild, FC2.Child[0]);
 end;
 
 procedure TTestReportChildren.TestSetParent4;
@@ -1582,7 +1579,9 @@ begin
   AssertEquals('Parent childcount is 1', 1, FC.ChildCount);
   AssertSame('Parent first child is OK', FChild, FC.Child[0]);
   FreeAndNil(FC);
-  AssertNull('Child parent was removed when parent is freed', FChild.Parent);
+  //FChild is freed due to free of parent
+  //AssertNull('Child parent was removed when parent is freed', FChild.Parent);
+  FChild := Nil;
 end;
 
 procedure TTestReportChildren.TestSetParent6;
@@ -2188,21 +2187,20 @@ var
   B: TFPReportCustomBand;
   P: TMyFPReportPage;
 begin
-  B := TFPReportCustomBand.Create(nil);
+  P := TMyFPReportPage.Create(nil);
   try
-    P := TMyFPReportPage.Create(nil);
+    B := TFPReportCustomBand.Create(nil);
     try
       B.Parent := P;
       AssertSame('Parent stored correctly', P, B.Page);
       AssertEquals('Bandcount correct', 1, P.BandCount);
       AssertSame('Bands[0] correct', B, P.Bands[0]);
     finally
-      P.Free;
+      B.Free;
     end;
-    AssertNull('Band notified that page is gone', B.Parent);
-    AssertNull('Band notified that page is gone', B.Page);
+    AssertEquals('Page notified that Band is gone', 0, P.BandCount);
   finally
-    B.Free;
+    P.Free;
   end;
 end;
 
@@ -2775,6 +2773,7 @@ end;
 
 procedure TTestCustomReport.TestBeginReportEvent;
 begin
+  TMyFPReportPage.Create(Report); // add at least one page
   Report.OnBeginReport := @HandleOnBeginReport;
   AssertEquals('Failed on 1', 0, FBeginReportCount);
   Report.RunReport;
@@ -2784,6 +2783,7 @@ end;
 
 procedure TTestCustomReport.TestEndReportEvent;
 begin
+  TMyFPReportPage.Create(Report); // add at least one page
   Report.OnEndReport := @HandleOnEndReport;
   AssertEquals('Failed on 1', 0, FEndReportCount);
   Report.RunReport;
@@ -2814,9 +2814,10 @@ begin
   AssertEquals('Failed on 3', 0, TMyFPReportPage(Report.Pages[2]).FPrepareObjectsCalled);
 
   Report.RunReport;
-  AssertEquals('Failed on 4', 1, TMyFPReportPage(Report.Pages[0]).FPrepareObjectsCalled);
-  AssertEquals('Failed on 5', 1, TMyFPReportPage(Report.Pages[1]).FPrepareObjectsCalled);
-  AssertEquals('Failed on 6', 1, TMyFPReportPage(Report.Pages[2]).FPrepareObjectsCalled);
+  // due to Re-interpret of Page.Data, page is prepared per record (r38906)
+  AssertEquals('Failed on 4', 2, TMyFPReportPage(Report.Pages[0]).FPrepareObjectsCalled);
+  AssertEquals('Failed on 5', 2, TMyFPReportPage(Report.Pages[1]).FPrepareObjectsCalled);
+  AssertEquals('Failed on 6', 2, TMyFPReportPage(Report.Pages[2]).FPrepareObjectsCalled);
 end;
 
 procedure TTestCustomReport.TestBandPrepareObjects;
@@ -2870,7 +2871,8 @@ begin
   AssertEquals('Failed on 1', 0, Report.RTObjects.Count);
 
   Report.RunReport;
-  AssertEquals('Failed on 2', 3, Report.RTObjects.Count);
+  // due to Re-interpret of Page.Data, page is prepared per record (r38906)
+  AssertEquals('Failed on 2', 6, Report.RTObjects.Count);
 end;
 
 procedure TTestCustomReport.TestRTObjects2;
@@ -2895,7 +2897,8 @@ begin
 
   AssertEquals('Failed on 1', 0, Report.RTObjects.Count);
   Report.RunReport;
-  AssertEquals('Failed on 2', 1, Report.RTObjects.Count); // runtime objects adhere to same hierarchy as design time
+  // due to Re-interpret of Page.Data, page is prepared per record (r38906)
+  AssertEquals('Failed on 2', 2, Report.RTObjects.Count); // runtime objects adhere to same hierarchy as design time
   AssertEquals('Failed on 3', 'TFPReportCustomPage', TObject(Report.RTObjects[0]).ClassName);
   rtPage := TFPReportCustomPage(Report.RTObjects[0]);
   AssertEquals('Failed on 4', 1, rtPage.ChildCount);
@@ -3104,7 +3107,7 @@ begin
   Memo := TFPReportMemo.Create(DataBand);
   Memo.Layout.Top := 5;
   Memo.Layout.Left := 10;
-  Memo.Text := '[recno]';
+  Memo.Text := '[recno('''')]';
 
   AssertEquals('Failed on 1', 0, Report.RTObjects.Count);
   Report.RunReport;
@@ -3404,6 +3407,7 @@ begin
   FMemo.UseParentFont := False;
   FMemo.Font.Name := 'Calibri';
   FMemo.StretchMode := smActualHeight;
+  FMemo.WordOverflow := woOverflow;
   TMemoFriend(FMemo).CreateRTLayout;
   TMemoFriend(FMemo).RecalcLayout;
   AssertEquals('Failed on 2', 2, FMemo.TextLines.Count);

+ 313 - 0
packages/fcl-report/test/tcreportgenerator.pas

@@ -0,0 +1,313 @@
+unit tcreportgenerator;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpcunit,
+  testregistry,
+  fpreport,
+  udapp,
+  fpTTF,
+  fpjson,
+  {demos}
+  rptsimplelist,
+  rptexpressions,
+  rptgrouping,
+  rptgrouping2,
+  rptframes,
+  rptimages,
+  rptttf,
+  rptshapes,
+  rptdataset,
+  rptcolumns,
+  rptmasterdetail,
+  rptjson,
+  rptcontnr,
+  rptnestedgroups,
+  rptBarcode,
+  rptQRcode;
+
+type
+
+  { TTestDemos }
+
+  TTestDemos = class(TTestCase)
+  private
+    FFilePath: String;
+    procedure SaveJSON(pFileName: String; pJSON: TJSONData);
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    procedure TestDemo(pName: String; pDemoAppClass: TReportDemoAppClass);
+  published
+    procedure SimpleList;
+    procedure ExpressionDemo;
+    procedure GroupingDemo;
+    procedure Grouping2Demo;
+    procedure FramesDemo;
+    procedure ImagesDemo;
+    procedure TTFDemo;
+    procedure ShapesDemo;
+    procedure DatasetDemo;
+    procedure ColumnsDemo;
+    procedure MasterDetailDemo;
+    procedure JSONDemo;
+    procedure CollectionDemo;
+    procedure ObjectListDemo;
+    procedure TestNestedGroupDemo;
+    procedure BarcodeDemo;
+    procedure QRCodeDemo;
+  end;
+
+
+implementation
+
+uses
+  fpjsonreport,
+  jsonscanner,
+  jsonparser;
+
+{ TTestDemos }
+
+procedure TTestDemos.SaveJSON(pFileName: String; pJSON: TJSONData);
+var
+  S: TFileStream;
+  J: TJSONStringType;
+begin
+  S:=TFileStream.Create(pFileName,fmCreate);
+  try
+    J:=pJSON.FormatJSON;
+    S.WriteBuffer(J[1],Length(J));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestDemos.SetUp;
+begin
+  inherited SetUp;
+  FFilePath:=ExtractFilePath(ParamStr(0));
+  if not ForceDirectories(FFilePath+'rendered') then
+     Fail('Could not create directory for rendered JSON');
+  gTTFontCache.Clear;
+  gTTFontCache.SearchPath.Add(FFilePath+'fonts/');
+  gTTFontCache.SearchPath.Add(FFilePath+'../demos/fonts/');
+{$IFDEF UNIX}
+  gTTFontCache.SearchPath.Add(GetUserDir + '.fonts/');
+  gTTFontCache.SearchPath.Add('/usr/share/fonts/truetype/ubuntu-font-family/');
+  gTTFontCache.SearchPath.Add('/usr/share/fonts/truetype/ubuntu/');
+  gTTFontCache.SearchPath.Add('/usr/share/fonts/truetype/dejavu/');
+{$ENDIF}
+  // ask to generate the font cache
+  gTTFontCache.BuildFontCache;
+end;
+
+procedure TTestDemos.TearDown;
+begin
+  inherited TearDown;
+end;
+
+procedure TTestDemos.TestDemo(pName: String; pDemoAppClass: TReportDemoAppClass);
+var
+  lApp: TReportDemoApp;
+  lSetJSON: TJSONData;
+  lActualJSON: TJSONObject;
+  S: TFileStream;
+  P: TJSONParser;
+  J: TJSONStringType;
+  lEqual: Boolean;
+  lSetFile, lActualFile: String;
+
+begin
+  lSetFile:=FFilePath+'rendered'+PathDelim+pName+'.set.json';
+  lActualFile:=FFilePath+'rendered'+PathDelim+pName+'.actual.json';
+
+
+  lApp:=pDemoAppClass.Create(Nil);
+  lActualJSON := TJSONObject.Create;
+  try
+    // delete old actual
+    DeleteFile(lActualFile);
+
+    // create Report
+    lApp.TestInit;
+    // run first time
+    lApp.rpt.RunReport;
+    lApp.rpt.SaveRenderToJSON(lActualJSON);
+
+    // delete DateCreated
+    lActualJSON.GetPath('Report.DateCreated').AsString := '';
+
+    //SaveJSON(lSetFile, lActualJSON); // uncomment for regeneration after changes
+    if Not FileExists(lSetFile) then
+       begin
+       SaveJSON(lSetFile, lActualJSON);
+       Ignore('No previous test result available, saved result for reference');
+       end;
+    // load set report
+    S:=TFileStream.Create(lSetFile,fmOpenRead);
+    try
+      P:=TJSONParser.Create(S, []);
+      try
+        lSetJSON:=TJSONObject(P.Parse);
+
+        // compare reports
+        lEqual := lSetJSON.AsJSON = lActualJSON.AsJSON;
+        if not lEqual then
+          SaveJSON(lActualFile, lActualJSON);
+        AssertTrue('equal renders', lEqual);
+
+        // run a second time
+        lApp.rpt.RunReport;
+        lActualJSON.Clear;
+        lApp.rpt.SaveRenderToJSON(lActualJSON);
+
+        // delete DateCreated
+        lActualJSON.GetPath('Report.DateCreated').AsString := '';
+
+        // compare reports
+        lEqual := lSetJSON.AsJSON = lActualJSON.AsJSON;
+        if not lEqual then
+          SaveJSON(lActualFile, lActualJSON);
+        AssertTrue('equal second renders', lEqual);
+      finally
+        lSetJSON.Free;
+        P.Free;
+      end;
+    finally
+      S.Free;
+    end;
+  finally
+    lActualJSON.Free;
+    lApp.Free;
+  end;
+end;
+
+procedure TTestDemos.SimpleList;
+begin
+  TestDemo('simplelist', TSimpleListDemo);
+end;
+
+procedure TTestDemos.ExpressionDemo;
+begin
+  TestDemo('expression', TExpressionsDemo);
+end;
+
+procedure TTestDemos.GroupingDemo;
+begin
+  TestDemo('grouping', TGroupingDemo);
+end;
+
+procedure TTestDemos.Grouping2Demo;
+begin
+  TestDemo('grouping2', TGrouping2Demo);
+end;
+
+procedure TTestDemos.FramesDemo;
+begin
+  TestDemo('frames', TFramesDemo);
+end;
+
+procedure TTestDemos.ImagesDemo;
+var
+  cd: String;
+begin
+  cd := GetCurrentDir;
+  SetCurrentDir(cd+PathDelim+'..'+PathDelim+'demos');
+  try
+    TestDemo('images', TImagesDemo);
+  finally
+    SetCurrentDir(cd);
+  end;
+end;
+
+procedure TTestDemos.TTFDemo;
+var
+  cd: String;
+begin
+  cd := GetCurrentDir;
+  SetCurrentDir(cd+PathDelim+'..'+PathDelim+'demos');
+  try
+    TestDemo('ttf', TTTFDemo);
+  finally
+    SetCurrentDir(cd);
+  end;
+end;
+
+procedure TTestDemos.ShapesDemo;
+begin
+  TestDemo('shapes', TShapesDemo);
+end;
+
+procedure TTestDemos.DatasetDemo;
+var
+  cd: String;
+begin
+  cd := GetCurrentDir;
+  SetCurrentDir(cd+PathDelim+'..'+PathDelim+'demos');
+  try
+    TestDemo('dataset', TDatasetDemo);
+  finally
+    SetCurrentDir(cd);
+  end;
+end;
+
+procedure TTestDemos.ColumnsDemo;
+begin
+  TestDemo('columns', TColumnsDemo)
+end;
+
+procedure TTestDemos.MasterDetailDemo;
+begin
+  TestDemo('masterdetail', TMasterDetailDemo);
+end;
+
+procedure TTestDemos.JSONDemo;
+var
+  cd: String;
+begin
+  cd := GetCurrentDir;
+  SetCurrentDir(cd+PathDelim+'..'+PathDelim+'demos');
+  try
+    TestDemo('json', TJSONDemo);
+  finally
+    SetCurrentDir(cd);
+  end;
+end;
+
+procedure TTestDemos.CollectionDemo;
+begin
+  TestDemo('collection', TCollectionDemo);
+end;
+
+procedure TTestDemos.ObjectListDemo;
+begin
+  TestDemo('objectlist', TObjectListDemo);
+end;
+
+procedure TTestDemos.BarcodeDemo;
+begin
+  TestDemo('barcode', TBarcodeDemo);
+end;
+
+procedure TTestDemos.QRCodeDemo;
+begin
+  TestDemo('qrcode', TQRCodeDemo);
+end;
+
+procedure TTestDemos.TestNestedGroupDemo;
+begin
+  TestDemo('nestedgroups', TNestedGroupsDemo);
+end;
+
+initialization
+  RegisterTests(
+    [TTestDemos
+    ]);
+
+end.
+

+ 1 - 1
packages/fcl-report/test/testfpreport.lpi

@@ -75,7 +75,7 @@
       <Filename Value="testfpreport"/>
     </Target>
     <SearchPaths>
-      <OtherUnitFiles Value="../src"/>
+      <OtherUnitFiles Value="../src;../demos;../demos/polygon"/>
       <UnitOutputDirectory Value="units"/>
     </SearchPaths>
     <Parsing>